Merge branch 'main' into feat/selectable-text
This commit is contained in:
@@ -14,7 +14,8 @@ async function main() {
|
|||||||
await server?.close()
|
await server?.close()
|
||||||
console.log('Starting new server')
|
console.log('Starting new server')
|
||||||
const inviteRequired = url?.query && 'invite' in url.query
|
const inviteRequired = url?.query && 'invite' in url.query
|
||||||
server = await createServer({inviteRequired})
|
const phoneRequired = url?.query && 'phone' in url.query
|
||||||
|
server = await createServer({inviteRequired, phoneRequired})
|
||||||
console.log('Listening at', server.pdsUrl)
|
console.log('Listening at', server.pdsUrl)
|
||||||
if (url?.query) {
|
if (url?.query) {
|
||||||
if ('users' in url.query) {
|
if ('users' in url.query) {
|
||||||
|
|||||||
@@ -16,14 +16,12 @@ describe('Create account', () => {
|
|||||||
|
|
||||||
await element(by.id('createAccountButton')).tap()
|
await element(by.id('createAccountButton')).tap()
|
||||||
await device.takeScreenshot('1- opened create account screen')
|
await device.takeScreenshot('1- opened create account screen')
|
||||||
await element(by.id('otherServerBtn')).tap()
|
await element(by.id('selectServiceButton')).tap()
|
||||||
await device.takeScreenshot('2- selected other server')
|
await device.takeScreenshot('2- selected other server')
|
||||||
await element(by.id('customServerInput')).clearText()
|
await element(by.id('customServerTextInput')).typeText(service)
|
||||||
await element(by.id('customServerInput')).typeText(service)
|
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||||
|
await element(by.id('customServerSelectBtn')).tap()
|
||||||
await device.takeScreenshot('3- input test server URL')
|
await device.takeScreenshot('3- input test server URL')
|
||||||
|
|
||||||
await element(by.id('nextBtn')).tap()
|
|
||||||
|
|
||||||
await element(by.id('emailInput')).typeText('example@test.com')
|
await element(by.id('emailInput')).typeText('example@test.com')
|
||||||
await element(by.id('passwordInput')).typeText('hunter2')
|
await element(by.id('passwordInput')).typeText('hunter2')
|
||||||
await device.takeScreenshot('4- entered account details')
|
await device.takeScreenshot('4- entered account details')
|
||||||
@@ -31,7 +29,7 @@ describe('Create account', () => {
|
|||||||
await element(by.id('nextBtn')).tap()
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
await element(by.id('handleInput')).typeText('e2e-test')
|
await element(by.id('handleInput')).typeText('e2e-test')
|
||||||
await device.takeScreenshot('4- entered handle')
|
await device.takeScreenshot('5- entered handle')
|
||||||
|
|
||||||
await element(by.id('nextBtn')).tap()
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
/* eslint-env detox/detox */
|
/* eslint-env detox/detox */
|
||||||
|
|
||||||
/**
|
|
||||||
* This test is being skipped until we can resolve the detox crash issue
|
|
||||||
* with the side drawer.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {describe, beforeAll, it} from '@jest/globals'
|
import {describe, beforeAll, it} from '@jest/globals'
|
||||||
import {expect} from 'detox'
|
import {expect} from 'detox'
|
||||||
import {openApp, loginAsAlice, createServer} from '../util'
|
import {openApp, loginAsAlice, createServer} from '../util'
|
||||||
@@ -31,12 +26,12 @@ describe('invite-codes', () => {
|
|||||||
await element(by.id('e2eOpenLoggedOutView')).tap()
|
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||||
await element(by.id('createAccountButton')).tap()
|
await element(by.id('createAccountButton')).tap()
|
||||||
await device.takeScreenshot('1- opened create account screen')
|
await device.takeScreenshot('1- opened create account screen')
|
||||||
await element(by.id('otherServerBtn')).tap()
|
await element(by.id('selectServiceButton')).tap()
|
||||||
await device.takeScreenshot('2- selected other server')
|
await device.takeScreenshot('2- selected other server')
|
||||||
await element(by.id('customServerInput')).clearText()
|
await element(by.id('customServerTextInput')).typeText(service)
|
||||||
await element(by.id('customServerInput')).typeText(service)
|
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||||
|
await element(by.id('customServerSelectBtn')).tap()
|
||||||
await device.takeScreenshot('3- input test server URL')
|
await device.takeScreenshot('3- input test server URL')
|
||||||
await element(by.id('nextBtn')).tap()
|
|
||||||
await element(by.id('inviteCodeInput')).typeText(inviteCode)
|
await element(by.id('inviteCodeInput')).typeText(inviteCode)
|
||||||
await element(by.id('emailInput')).typeText('example@test.com')
|
await element(by.id('emailInput')).typeText('example@test.com')
|
||||||
await element(by.id('passwordInput')).typeText('hunter2')
|
await element(by.id('passwordInput')).typeText('hunter2')
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/* eslint-env detox/detox */
|
||||||
|
|
||||||
|
import {describe, beforeAll, it} from '@jest/globals'
|
||||||
|
import {expect} from 'detox'
|
||||||
|
import {openApp, loginAsAlice, createServer} from '../util'
|
||||||
|
|
||||||
|
describe('invite-codes', () => {
|
||||||
|
let service: string
|
||||||
|
let inviteCode = ''
|
||||||
|
beforeAll(async () => {
|
||||||
|
service = await createServer('?users&invite&phone')
|
||||||
|
await openApp({permissions: {notifications: 'YES'}})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('I can fetch invite codes', async () => {
|
||||||
|
await loginAsAlice()
|
||||||
|
await element(by.id('e2eOpenInviteCodesModal')).tap()
|
||||||
|
await expect(element(by.id('inviteCodesModal'))).toBeVisible()
|
||||||
|
const attrs = await element(by.id('inviteCode-0-code')).getAttributes()
|
||||||
|
inviteCode = attrs.text
|
||||||
|
await element(by.id('closeBtn')).tap()
|
||||||
|
await element(by.id('e2eSignOut')).tap()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('I can create a new account with the invite code', async () => {
|
||||||
|
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||||
|
await element(by.id('createAccountButton')).tap()
|
||||||
|
await device.takeScreenshot('1- opened create account screen')
|
||||||
|
await element(by.id('selectServiceButton')).tap()
|
||||||
|
await device.takeScreenshot('2- selected other server')
|
||||||
|
await element(by.id('customServerTextInput')).typeText(service)
|
||||||
|
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||||
|
await element(by.id('customServerSelectBtn')).tap()
|
||||||
|
await device.takeScreenshot('3- input test server URL')
|
||||||
|
await element(by.id('inviteCodeInput')).typeText(inviteCode)
|
||||||
|
await element(by.id('emailInput')).typeText('example@test.com')
|
||||||
|
await element(by.id('passwordInput')).typeText('hunter2')
|
||||||
|
await device.takeScreenshot('4- entered account details')
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
await element(by.id('phoneInput')).typeText('5558675309')
|
||||||
|
await element(by.id('requestCodeBtn')).tap()
|
||||||
|
await device.takeScreenshot('5- requested code')
|
||||||
|
await element(by.id('codeInput')).typeText('000000')
|
||||||
|
await device.takeScreenshot('6- entered code')
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
await element(by.id('handleInput')).typeText('e2e-test')
|
||||||
|
await device.takeScreenshot('7- entered handle')
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
await expect(element(by.id('welcomeOnboarding'))).toBeVisible()
|
||||||
|
await element(by.id('continueBtn')).tap()
|
||||||
|
await expect(element(by.id('recommendedFeedsOnboarding'))).toBeVisible()
|
||||||
|
await element(by.id('continueBtn')).tap()
|
||||||
|
await expect(element(by.id('recommendedFollowsOnboarding'))).toBeVisible()
|
||||||
|
await element(by.id('continueBtn')).tap()
|
||||||
|
await expect(element(by.id('homeScreen'))).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/* eslint-env detox/detox */
|
||||||
|
|
||||||
|
import {describe, beforeAll, it} from '@jest/globals'
|
||||||
|
import {expect} from 'detox'
|
||||||
|
import {openApp, createServer} from '../util'
|
||||||
|
|
||||||
|
describe('Create account', () => {
|
||||||
|
let service: string
|
||||||
|
beforeAll(async () => {
|
||||||
|
service = await createServer('?phone')
|
||||||
|
await openApp({permissions: {notifications: 'YES'}})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('I can create a new account with text verification', async () => {
|
||||||
|
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||||
|
|
||||||
|
await element(by.id('createAccountButton')).tap()
|
||||||
|
await device.takeScreenshot('1- opened create account screen')
|
||||||
|
await element(by.id('selectServiceButton')).tap()
|
||||||
|
await device.takeScreenshot('2- selected other server')
|
||||||
|
await element(by.id('customServerTextInput')).typeText(service)
|
||||||
|
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||||
|
await element(by.id('customServerSelectBtn')).tap()
|
||||||
|
await device.takeScreenshot('3- input test server URL')
|
||||||
|
await element(by.id('emailInput')).typeText('text-verification@test.com')
|
||||||
|
await element(by.id('passwordInput')).typeText('hunter2')
|
||||||
|
await device.takeScreenshot('4- entered account details')
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
|
await element(by.id('phoneInput')).typeText('1234567890')
|
||||||
|
await element(by.id('requestCodeBtn')).tap()
|
||||||
|
await device.takeScreenshot('5- requested code')
|
||||||
|
|
||||||
|
await element(by.id('codeInput')).typeText('000000')
|
||||||
|
await device.takeScreenshot('6- entered code')
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
|
await element(by.id('handleInput')).typeText('text-verification-test')
|
||||||
|
await device.takeScreenshot('7- entered handle')
|
||||||
|
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
|
await expect(element(by.id('welcomeOnboarding'))).toBeVisible()
|
||||||
|
await element(by.id('continueBtn')).tap()
|
||||||
|
await expect(element(by.id('recommendedFeedsOnboarding'))).toBeVisible()
|
||||||
|
await element(by.id('continueBtn')).tap()
|
||||||
|
await expect(element(by.id('recommendedFollowsOnboarding'))).toBeVisible()
|
||||||
|
await element(by.id('continueBtn')).tap()
|
||||||
|
await expect(element(by.id('homeScreen'))).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('failed text verification correctly goes back to the code input screen', async () => {
|
||||||
|
await element(by.id('e2eSignOut')).tap()
|
||||||
|
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||||
|
|
||||||
|
await element(by.id('createAccountButton')).tap()
|
||||||
|
await device.takeScreenshot('1- opened create account screen')
|
||||||
|
await element(by.id('selectServiceButton')).tap()
|
||||||
|
await device.takeScreenshot('2- selected other server')
|
||||||
|
await element(by.id('customServerTextInput')).typeText(service)
|
||||||
|
await element(by.id('customServerTextInput')).tapReturnKey()
|
||||||
|
await element(by.id('customServerSelectBtn')).tap()
|
||||||
|
await device.takeScreenshot('3- input test server URL')
|
||||||
|
await element(by.id('emailInput')).typeText('text-verification2@test.com')
|
||||||
|
await element(by.id('passwordInput')).typeText('hunter2')
|
||||||
|
await device.takeScreenshot('4- entered account details')
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
|
await element(by.id('phoneInput')).typeText('1234567890')
|
||||||
|
await element(by.id('requestCodeBtn')).tap()
|
||||||
|
await device.takeScreenshot('5- requested code')
|
||||||
|
|
||||||
|
await element(by.id('codeInput')).typeText('111111')
|
||||||
|
await device.takeScreenshot('6- entered code')
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
|
await element(by.id('handleInput')).typeText('text-verification-test2')
|
||||||
|
await device.takeScreenshot('7- entered handle')
|
||||||
|
|
||||||
|
await element(by.id('nextBtn')).tap()
|
||||||
|
|
||||||
|
await expect(element(by.id('codeInput'))).toBeVisible()
|
||||||
|
await device.takeScreenshot('8- got error')
|
||||||
|
})
|
||||||
|
})
|
||||||
+1
-1
@@ -105,7 +105,7 @@ async function openAppForDebugBuild(platform: string, opts: any) {
|
|||||||
await sleep(3000)
|
await sleep(3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createServer(path = '') {
|
export async function createServer(path = ''): Promise<string> {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var req = http.request(
|
var req = http.request(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -463,44 +463,44 @@ describe('parseEmbedPlayerFromUrl', () => {
|
|||||||
type: 'youtube_video',
|
type: 'youtube_video',
|
||||||
source: 'youtube',
|
source: 'youtube',
|
||||||
playerUri:
|
playerUri:
|
||||||
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1',
|
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1&start=0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'youtube_video',
|
type: 'youtube_video',
|
||||||
source: 'youtube',
|
source: 'youtube',
|
||||||
playerUri:
|
playerUri:
|
||||||
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1',
|
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1&start=0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'youtube_video',
|
type: 'youtube_video',
|
||||||
source: 'youtube',
|
source: 'youtube',
|
||||||
playerUri:
|
playerUri:
|
||||||
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1',
|
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1&start=0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'youtube_video',
|
type: 'youtube_video',
|
||||||
source: 'youtube',
|
source: 'youtube',
|
||||||
playerUri:
|
playerUri:
|
||||||
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1',
|
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1&start=0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'youtube_video',
|
type: 'youtube_video',
|
||||||
source: 'youtube',
|
source: 'youtube',
|
||||||
playerUri:
|
playerUri:
|
||||||
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1',
|
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1&start=0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'youtube_short',
|
type: 'youtube_short',
|
||||||
source: 'youtubeShorts',
|
source: 'youtubeShorts',
|
||||||
hideDetails: true,
|
hideDetails: true,
|
||||||
playerUri:
|
playerUri:
|
||||||
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1',
|
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1&start=0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'youtube_video',
|
type: 'youtube_video',
|
||||||
source: 'youtube',
|
source: 'youtube',
|
||||||
playerUri:
|
playerUri:
|
||||||
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1',
|
'https://www.youtube.com/embed/videoId?autoplay=1&playsinline=1&start=0',
|
||||||
},
|
},
|
||||||
|
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ module.exports = function () {
|
|||||||
/**
|
/**
|
||||||
* Android build number. Must be incremented for each release.
|
* Android build number. Must be incremented for each release.
|
||||||
*/
|
*/
|
||||||
const ANDROID_VERSION_CODE = 55
|
const ANDROID_VERSION_CODE = 57
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses built-in Expo env vars
|
* Uses built-in Expo env vars
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M8 6a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v9a1 1 0 1 1-2 0V8.414l-9.793 9.793a1 1 0 0 1-1.414-1.414L15.586 7H9a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 260 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Zm1 16V9h14v10H5ZM5 7h14V5H5v2Zm3 10.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM17.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM12 13.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM9.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM12 17.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 512 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 12c0-4.09 3.527-7.5 8-7.5s8 3.41 8 7.5c0 1.579-.419 2.056-.708 2.236-.388.241-1.031.286-2.058.153-.33-.043-.652-.096-.991-.152a65.905 65.905 0 0 0-.531-.087c-.52-.081-1.077-.156-1.61-.164-1.065-.016-2.336.245-2.996 1.567-.418.834-.295 1.67-.078 2.314.18.534.47 1.055.683 1.437v.001l.097.175.01.018C7.432 19.407 4 16.033 4 12Zm8-9.5C6.532 2.5 2 6.7 2 12s4.532 9.5 10 9.5c.401 0 .812-.04 1.166-.193.41-.176.761-.517.866-1.028.085-.416-.03-.796-.118-1.029a5.981 5.981 0 0 0-.351-.73l-.12-.215c-.215-.392-.403-.73-.52-1.078-.13-.387-.111-.614-.029-.78.146-.291.404-.473 1.178-.461.385.005.825.06 1.329.14.15.023.308.05.47.077.36.059.742.122 1.105.17 1.021.132 2.325.213 3.373-.439C21.496 15.22 22 13.874 22 12c0-5.3-4.532-9.5-10-9.5Zm3.5 8.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM9 12.25a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm1.5-2.75a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 1011 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4.062 11h2.961c.103-2.204.545-4.218 1.235-5.77.06-.136.123-.269.188-.399A8.007 8.007 0 0 0 4.062 11ZM12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm0 2c-.227 0-.518.1-.868.432-.354.337-.719.872-1.047 1.61-.561 1.263-.958 2.991-1.06 4.958h5.95c-.102-1.967-.499-3.695-1.06-4.958-.328-.738-.693-1.273-1.047-1.61C12.518 4.099 12.227 4 12 4Zm4.977 7c-.103-2.204-.545-4.218-1.235-5.77a9.78 9.78 0 0 0-.188-.399A8.006 8.006 0 0 1 19.938 11h-2.961Zm-2.003 2H9.026c.101 1.966.498 3.695 1.06 4.958.327.738.692 1.273 1.046 1.61.35.333.641.432.868.432.227 0 .518-.1.868-.432.354-.337.719-.872 1.047-1.61.561-1.263.958-2.991 1.06-4.958Zm.58 6.169c.065-.13.128-.263.188-.399.69-1.552 1.132-3.566 1.235-5.77h2.961a8.006 8.006 0 0 1-4.384 6.169Zm-7.108 0a9.877 9.877 0 0 1-.188-.399c-.69-1.552-1.132-3.566-1.235-5.77H4.062a8.006 8.006 0 0 0 4.384 6.169Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 1005 B |
@@ -80,7 +80,7 @@ func (srv *Server) WebProfileRSS(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, did.String(), "", "", 30)
|
af, err := appbsky.FeedGetAuthorFeed(ctx, srv.xrpcc, did.String(), "", "posts_no_replies", 30)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("failed to fetch author feed", "did", did, "err", err)
|
log.Warn("failed to fetch author feed", "did", did, "err", err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -39,30 +39,49 @@
|
|||||||
height: calc(100% + env(safe-area-inset-top));
|
height: calc(100% + env(safe-area-inset-top));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove autofill styles on Webkit */
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
textarea:-webkit-autofill,
|
||||||
|
textarea:-webkit-autofill:hover,
|
||||||
|
textarea:-webkit-autofill:focus,
|
||||||
|
select:-webkit-autofill,
|
||||||
|
select:-webkit-autofill:hover,
|
||||||
|
select:-webkit-autofill:focus {
|
||||||
|
border: 0;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
}
|
||||||
|
/* Force left-align date/time inputs on iOS mobile */
|
||||||
|
input::-webkit-date-and-time-value {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
/* Color theming */
|
/* Color theming */
|
||||||
:root {
|
:root {
|
||||||
--text: black;
|
--text: black;
|
||||||
--background: white;
|
--background: white;
|
||||||
--backgroundLight: #F3F3F8;
|
--backgroundLight: hsl(211, 20%, 95%);
|
||||||
}
|
}
|
||||||
html.colorMode--dark {
|
html.colorMode--dark {
|
||||||
--text: white;
|
--text: white;
|
||||||
--background: black;
|
--background: hsl(211, 20%, 4%);
|
||||||
--backgroundLight: #26272D;
|
--backgroundLight: hsl(211, 20%, 20%);
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
html.colorMode--system {
|
html.colorMode--system {
|
||||||
--text: black;
|
--text: black;
|
||||||
--background: white;
|
--background: white;
|
||||||
--backgroundLight: #F3F3F8;
|
--backgroundLight: hsl(211, 20%, 95%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
html.colorMode--system {
|
html.colorMode--system {
|
||||||
--text: white;
|
--text: white;
|
||||||
--background: black;
|
--background: hsl(211, 20%, 4%);
|
||||||
--backgroundLight: #26272D;
|
--backgroundLight: hsl(211, 20%, 20%);
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-3
@@ -1,7 +1,7 @@
|
|||||||
import net from 'net'
|
import net from 'net'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import {TestNetwork} from '@atproto/dev-env'
|
import {TestNetwork, TestPds} from '@atproto/dev-env'
|
||||||
import {AtUri, BskyAgent} from '@atproto/api'
|
import {AtUri, BskyAgent} from '@atproto/api'
|
||||||
|
|
||||||
export interface TestUser {
|
export interface TestUser {
|
||||||
@@ -55,19 +55,36 @@ class StringIdGenerator {
|
|||||||
const ids = new StringIdGenerator()
|
const ids = new StringIdGenerator()
|
||||||
|
|
||||||
export async function createServer(
|
export async function createServer(
|
||||||
{inviteRequired}: {inviteRequired: boolean} = {inviteRequired: false},
|
{
|
||||||
|
inviteRequired,
|
||||||
|
phoneRequired,
|
||||||
|
}: {inviteRequired: boolean; phoneRequired: boolean} = {
|
||||||
|
inviteRequired: false,
|
||||||
|
phoneRequired: false,
|
||||||
|
},
|
||||||
): Promise<TestPDS> {
|
): Promise<TestPDS> {
|
||||||
const port = await getPort()
|
const port = 3000
|
||||||
const port2 = await getPort(port + 1)
|
const port2 = await getPort(port + 1)
|
||||||
const port3 = await getPort(port2 + 1)
|
const port3 = await getPort(port2 + 1)
|
||||||
const pdsUrl = `http://localhost:${port}`
|
const pdsUrl = `http://localhost:${port}`
|
||||||
const id = ids.next()
|
const id = ids.next()
|
||||||
|
|
||||||
|
const phoneParams = phoneRequired
|
||||||
|
? {
|
||||||
|
phoneVerificationRequired: true,
|
||||||
|
twilioAccountSid: 'ACXXXXXXX',
|
||||||
|
twilioAuthToken: 'AUTH',
|
||||||
|
twilioServiceSid: 'VAXXXXXXXX',
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
|
||||||
const testNet = await TestNetwork.create({
|
const testNet = await TestNetwork.create({
|
||||||
pds: {
|
pds: {
|
||||||
port,
|
port,
|
||||||
hostname: 'localhost',
|
hostname: 'localhost',
|
||||||
|
dbPostgresSchema: `pds_${id}`,
|
||||||
inviteRequired,
|
inviteRequired,
|
||||||
|
...phoneParams,
|
||||||
},
|
},
|
||||||
bsky: {
|
bsky: {
|
||||||
dbPostgresSchema: `bsky_${id}`,
|
dbPostgresSchema: `bsky_${id}`,
|
||||||
@@ -76,6 +93,7 @@ export async function createServer(
|
|||||||
},
|
},
|
||||||
plc: {port: port2},
|
plc: {port: port2},
|
||||||
})
|
})
|
||||||
|
mockTwilio(testNet.pds)
|
||||||
|
|
||||||
const pic = fs.readFileSync(
|
const pic = fs.readFileSync(
|
||||||
path.join(__dirname, '..', 'assets', 'default-avatar.png'),
|
path.join(__dirname, '..', 'assets', 'default-avatar.png'),
|
||||||
@@ -144,6 +162,8 @@ class Mocker {
|
|||||||
email,
|
email,
|
||||||
handle: name + '.test',
|
handle: name + '.test',
|
||||||
password: 'hunter2',
|
password: 'hunter2',
|
||||||
|
verificationPhone: '1234567890',
|
||||||
|
verificationCode: '000000',
|
||||||
})
|
})
|
||||||
await agent.upsertProfile(async () => {
|
await agent.upsertProfile(async () => {
|
||||||
const blob = await agent.uploadBlob(this.pic, {
|
const blob = await agent.uploadBlob(this.pic, {
|
||||||
@@ -430,3 +450,15 @@ async function getPort(start = 3000) {
|
|||||||
}
|
}
|
||||||
throw new Error('Unable to find an available port')
|
throw new Error('Unable to find an available port')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mockTwilio = (pds: TestPds) => {
|
||||||
|
if (!pds.ctx.twilio) return
|
||||||
|
|
||||||
|
pds.ctx.twilio.sendCode = async (_number: string) => {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
pds.ctx.twilio.verifyCode = async (_number: string, code: string) => {
|
||||||
|
return code === '000000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+13
-1
@@ -1,6 +1,18 @@
|
|||||||
/** @type {import('@lingui/conf').LinguiConfig} */
|
/** @type {import('@lingui/conf').LinguiConfig} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
locales: ['en', 'de', 'es', 'fr', 'hi', 'id', 'ja', 'ko', 'pt-BR', 'uk'],
|
locales: [
|
||||||
|
'en',
|
||||||
|
'de',
|
||||||
|
'es',
|
||||||
|
'fr',
|
||||||
|
'hi',
|
||||||
|
'id',
|
||||||
|
'ja',
|
||||||
|
'ko',
|
||||||
|
'pt-BR',
|
||||||
|
'uk',
|
||||||
|
'ca',
|
||||||
|
],
|
||||||
catalogs: [
|
catalogs: [
|
||||||
{
|
{
|
||||||
path: '<rootDir>/src/locale/locales/{locale}/messages',
|
path: '<rootDir>/src/locale/locales/{locale}/messages',
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bsky.app",
|
"name": "bsky.app",
|
||||||
"version": "1.64.0",
|
"version": "1.65.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android"
|
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.8.0",
|
"@atproto/api": "^0.9.1",
|
||||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
"@segment/analytics-react-native": "^2.10.1",
|
"@segment/analytics-react-native": "^2.10.1",
|
||||||
"@segment/sovran-react-native": "^0.4.5",
|
"@segment/sovran-react-native": "^0.4.5",
|
||||||
"@sentry/react-native": "5.5.0",
|
"@sentry/react-native": "5.5.0",
|
||||||
|
"@tamagui/focus-scope": "^1.84.1",
|
||||||
"@tanstack/react-query": "^5.8.1",
|
"@tanstack/react-query": "^5.8.1",
|
||||||
"@tiptap/core": "^2.0.0-beta.220",
|
"@tiptap/core": "^2.0.0-beta.220",
|
||||||
"@tiptap/extension-document": "^2.0.0-beta.220",
|
"@tiptap/extension-document": "^2.0.0-beta.220",
|
||||||
@@ -119,6 +120,7 @@
|
|||||||
"js-sha256": "^0.9.0",
|
"js-sha256": "^0.9.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lande": "^1.0.10",
|
"lande": "^1.0.10",
|
||||||
|
"libphonenumber-js": "^1.10.53",
|
||||||
"lodash.chunk": "^4.2.0",
|
"lodash.chunk": "^4.2.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
diff --git a/node_modules/@lingui/core/dist/index.mjs b/node_modules/@lingui/core/dist/index.mjs
|
||||||
|
index 9759736..881f67b 100644
|
||||||
|
--- a/node_modules/@lingui/core/dist/index.mjs
|
||||||
|
+++ b/node_modules/@lingui/core/dist/index.mjs
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-import unraw from 'unraw';
|
||||||
|
+import { unraw } from 'unraw';
|
||||||
|
import { compileMessage } from '@lingui/message-utils/compileMessage';
|
||||||
|
|
||||||
|
const isString = (s) => typeof s === "string";
|
||||||
+35
-24
@@ -13,6 +13,8 @@ import {
|
|||||||
|
|
||||||
import 'view/icons'
|
import 'view/icons'
|
||||||
|
|
||||||
|
import {ThemeProvider as Alf} from '#/alf'
|
||||||
|
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||||
import {init as initPersistedState} from '#/state/persisted'
|
import {init as initPersistedState} from '#/state/persisted'
|
||||||
import {listenSessionDropped} from './state/events'
|
import {listenSessionDropped} from './state/events'
|
||||||
import {useColorMode} from 'state/shell'
|
import {useColorMode} from 'state/shell'
|
||||||
@@ -25,6 +27,7 @@ import {queryClient} from 'lib/react-query'
|
|||||||
import {TestCtrls} from 'view/com/testing/TestCtrls'
|
import {TestCtrls} from 'view/com/testing/TestCtrls'
|
||||||
import {Provider as ShellStateProvider} from 'state/shell'
|
import {Provider as ShellStateProvider} from 'state/shell'
|
||||||
import {Provider as ModalStateProvider} from 'state/modals'
|
import {Provider as ModalStateProvider} from 'state/modals'
|
||||||
|
import {Provider as DialogStateProvider} from 'state/dialogs'
|
||||||
import {Provider as LightboxStateProvider} from 'state/lightbox'
|
import {Provider as LightboxStateProvider} from 'state/lightbox'
|
||||||
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||||
import {Provider as InvitesStateProvider} from 'state/invites'
|
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||||
@@ -39,6 +42,7 @@ import {
|
|||||||
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
|
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {Splash} from '#/Splash'
|
import {Splash} from '#/Splash'
|
||||||
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -48,6 +52,7 @@ function InnerApp() {
|
|||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const {isInitialLoad, currentAccount} = useSession()
|
const {isInitialLoad, currentAccount} = useSession()
|
||||||
const {resumeSession} = useSessionApi()
|
const {resumeSession} = useSessionApi()
|
||||||
|
const theme = useColorModeTheme(colorMode)
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
// init
|
// init
|
||||||
@@ -63,25 +68,27 @@ function InnerApp() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
||||||
<Splash isReady={!isInitialLoad}>
|
<Alf theme={theme}>
|
||||||
<React.Fragment
|
<Splash isReady={!isInitialLoad}>
|
||||||
// Resets the entire tree below when it changes:
|
<React.Fragment
|
||||||
key={currentAccount?.did}>
|
// Resets the entire tree below when it changes:
|
||||||
<LoggedOutViewProvider>
|
key={currentAccount?.did}>
|
||||||
<UnreadNotifsProvider>
|
<LoggedOutViewProvider>
|
||||||
<ThemeProvider theme={colorMode}>
|
<UnreadNotifsProvider>
|
||||||
{/* All components should be within this provider */}
|
<ThemeProvider theme={colorMode}>
|
||||||
<RootSiblingParent>
|
{/* All components should be within this provider */}
|
||||||
<GestureHandlerRootView style={s.h100pct}>
|
<RootSiblingParent>
|
||||||
<TestCtrls />
|
<GestureHandlerRootView style={s.h100pct}>
|
||||||
<Shell />
|
<TestCtrls />
|
||||||
</GestureHandlerRootView>
|
<Shell />
|
||||||
</RootSiblingParent>
|
</GestureHandlerRootView>
|
||||||
</ThemeProvider>
|
</RootSiblingParent>
|
||||||
</UnreadNotifsProvider>
|
</ThemeProvider>
|
||||||
</LoggedOutViewProvider>
|
</UnreadNotifsProvider>
|
||||||
</React.Fragment>
|
</LoggedOutViewProvider>
|
||||||
</Splash>
|
</React.Fragment>
|
||||||
|
</Splash>
|
||||||
|
</Alf>
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -109,11 +116,15 @@ function App() {
|
|||||||
<MutedThreadsProvider>
|
<MutedThreadsProvider>
|
||||||
<InvitesStateProvider>
|
<InvitesStateProvider>
|
||||||
<ModalStateProvider>
|
<ModalStateProvider>
|
||||||
<LightboxStateProvider>
|
<DialogStateProvider>
|
||||||
<I18nProvider>
|
<LightboxStateProvider>
|
||||||
<InnerApp />
|
<I18nProvider>
|
||||||
</I18nProvider>
|
<PortalProvider>
|
||||||
</LightboxStateProvider>
|
<InnerApp />
|
||||||
|
</PortalProvider>
|
||||||
|
</I18nProvider>
|
||||||
|
</LightboxStateProvider>
|
||||||
|
</DialogStateProvider>
|
||||||
</ModalStateProvider>
|
</ModalStateProvider>
|
||||||
</InvitesStateProvider>
|
</InvitesStateProvider>
|
||||||
</MutedThreadsProvider>
|
</MutedThreadsProvider>
|
||||||
|
|||||||
+12
-6
@@ -8,6 +8,7 @@ import {RootSiblingParent} from 'react-native-root-siblings'
|
|||||||
import 'view/icons'
|
import 'view/icons'
|
||||||
|
|
||||||
import {ThemeProvider as Alf} from '#/alf'
|
import {ThemeProvider as Alf} from '#/alf'
|
||||||
|
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||||
import {init as initPersistedState} from '#/state/persisted'
|
import {init as initPersistedState} from '#/state/persisted'
|
||||||
import {useColorMode} from 'state/shell'
|
import {useColorMode} from 'state/shell'
|
||||||
import {Shell} from 'view/shell/index'
|
import {Shell} from 'view/shell/index'
|
||||||
@@ -16,6 +17,7 @@ import {ThemeProvider} from 'lib/ThemeContext'
|
|||||||
import {queryClient} from 'lib/react-query'
|
import {queryClient} from 'lib/react-query'
|
||||||
import {Provider as ShellStateProvider} from 'state/shell'
|
import {Provider as ShellStateProvider} from 'state/shell'
|
||||||
import {Provider as ModalStateProvider} from 'state/modals'
|
import {Provider as ModalStateProvider} from 'state/modals'
|
||||||
|
import {Provider as DialogStateProvider} from 'state/dialogs'
|
||||||
import {Provider as LightboxStateProvider} from 'state/lightbox'
|
import {Provider as LightboxStateProvider} from 'state/lightbox'
|
||||||
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||||
import {Provider as InvitesStateProvider} from 'state/invites'
|
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||||
@@ -29,7 +31,7 @@ import {
|
|||||||
} from 'state/session'
|
} from 'state/session'
|
||||||
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
|
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
|
|
||||||
function InnerApp() {
|
function InnerApp() {
|
||||||
const {isInitialLoad, currentAccount} = useSession()
|
const {isInitialLoad, currentAccount} = useSession()
|
||||||
@@ -92,11 +94,15 @@ function App() {
|
|||||||
<MutedThreadsProvider>
|
<MutedThreadsProvider>
|
||||||
<InvitesStateProvider>
|
<InvitesStateProvider>
|
||||||
<ModalStateProvider>
|
<ModalStateProvider>
|
||||||
<LightboxStateProvider>
|
<DialogStateProvider>
|
||||||
<I18nProvider>
|
<LightboxStateProvider>
|
||||||
<InnerApp />
|
<I18nProvider>
|
||||||
</I18nProvider>
|
<PortalProvider>
|
||||||
</LightboxStateProvider>
|
<InnerApp />
|
||||||
|
</PortalProvider>
|
||||||
|
</I18nProvider>
|
||||||
|
</LightboxStateProvider>
|
||||||
|
</DialogStateProvider>
|
||||||
</ModalStateProvider>
|
</ModalStateProvider>
|
||||||
</InvitesStateProvider>
|
</InvitesStateProvider>
|
||||||
</MutedThreadsProvider>
|
</MutedThreadsProvider>
|
||||||
|
|||||||
+4
-4
@@ -61,7 +61,7 @@ import {ProfileListScreen} from './view/screens/ProfileList'
|
|||||||
import {PostThreadScreen} from './view/screens/PostThread'
|
import {PostThreadScreen} from './view/screens/PostThread'
|
||||||
import {PostLikedByScreen} from './view/screens/PostLikedBy'
|
import {PostLikedByScreen} from './view/screens/PostLikedBy'
|
||||||
import {PostRepostedByScreen} from './view/screens/PostRepostedBy'
|
import {PostRepostedByScreen} from './view/screens/PostRepostedBy'
|
||||||
import {DebugScreen} from './view/screens/DebugNew'
|
import {Storybook} from './view/screens/Storybook'
|
||||||
import {LogScreen} from './view/screens/Log'
|
import {LogScreen} from './view/screens/Log'
|
||||||
import {SupportScreen} from './view/screens/Support'
|
import {SupportScreen} from './view/screens/Support'
|
||||||
import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy'
|
import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy'
|
||||||
@@ -144,7 +144,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
|||||||
name="Profile"
|
name="Profile"
|
||||||
getComponent={() => ProfileScreen}
|
getComponent={() => ProfileScreen}
|
||||||
options={({route}) => ({
|
options={({route}) => ({
|
||||||
title: title(msg`@${route.params.name}`),
|
title: bskyTitle(`@${route.params.name}`, unreadCountLabel),
|
||||||
animation: 'none',
|
animation: 'none',
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -200,8 +200,8 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
|||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="Debug"
|
name="Debug"
|
||||||
getComponent={() => DebugScreen}
|
getComponent={() => Storybook}
|
||||||
options={{title: title(msg`Debug`), requireAuth: true}}
|
options={{title: title(msg`Storybook`), requireAuth: true}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="Log"
|
name="Log"
|
||||||
|
|||||||
+266
-78
@@ -4,6 +4,9 @@ export const atoms = {
|
|||||||
/*
|
/*
|
||||||
* Positioning
|
* Positioning
|
||||||
*/
|
*/
|
||||||
|
fixed: {
|
||||||
|
position: 'fixed',
|
||||||
|
},
|
||||||
absolute: {
|
absolute: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
},
|
},
|
||||||
@@ -32,6 +35,10 @@ export const atoms = {
|
|||||||
zIndex: 50,
|
zIndex: 50,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
overflow_hidden: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Width
|
* Width
|
||||||
*/
|
*/
|
||||||
@@ -45,6 +52,12 @@ export const atoms = {
|
|||||||
/*
|
/*
|
||||||
* Border radius
|
* Border radius
|
||||||
*/
|
*/
|
||||||
|
rounded_2xs: {
|
||||||
|
borderRadius: tokens.borderRadius._2xs,
|
||||||
|
},
|
||||||
|
rounded_xs: {
|
||||||
|
borderRadius: tokens.borderRadius.xs,
|
||||||
|
},
|
||||||
rounded_sm: {
|
rounded_sm: {
|
||||||
borderRadius: tokens.borderRadius.sm,
|
borderRadius: tokens.borderRadius.sm,
|
||||||
},
|
},
|
||||||
@@ -58,8 +71,8 @@ export const atoms = {
|
|||||||
/*
|
/*
|
||||||
* Flex
|
* Flex
|
||||||
*/
|
*/
|
||||||
gap_xxs: {
|
gap_2xs: {
|
||||||
gap: tokens.space.xxs,
|
gap: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
gap_xs: {
|
gap_xs: {
|
||||||
gap: tokens.space.xs,
|
gap: tokens.space.xs,
|
||||||
@@ -76,8 +89,17 @@ export const atoms = {
|
|||||||
gap_xl: {
|
gap_xl: {
|
||||||
gap: tokens.space.xl,
|
gap: tokens.space.xl,
|
||||||
},
|
},
|
||||||
gap_xxl: {
|
gap_2xl: {
|
||||||
gap: tokens.space.xxl,
|
gap: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
gap_3xl: {
|
||||||
|
gap: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
gap_4xl: {
|
||||||
|
gap: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
gap_5xl: {
|
||||||
|
gap: tokens.space._5xl,
|
||||||
},
|
},
|
||||||
flex: {
|
flex: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -125,9 +147,9 @@ export const atoms = {
|
|||||||
text_right: {
|
text_right: {
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
},
|
},
|
||||||
text_xxs: {
|
text_2xs: {
|
||||||
fontSize: tokens.fontSize.xxs,
|
fontSize: tokens.fontSize._2xs,
|
||||||
lineHeight: tokens.fontSize.xxs,
|
lineHeight: tokens.fontSize._2xs,
|
||||||
},
|
},
|
||||||
text_xs: {
|
text_xs: {
|
||||||
fontSize: tokens.fontSize.xs,
|
fontSize: tokens.fontSize.xs,
|
||||||
@@ -149,9 +171,21 @@ export const atoms = {
|
|||||||
fontSize: tokens.fontSize.xl,
|
fontSize: tokens.fontSize.xl,
|
||||||
lineHeight: tokens.fontSize.xl,
|
lineHeight: tokens.fontSize.xl,
|
||||||
},
|
},
|
||||||
text_xxl: {
|
text_2xl: {
|
||||||
fontSize: tokens.fontSize.xxl,
|
fontSize: tokens.fontSize._2xl,
|
||||||
lineHeight: tokens.fontSize.xxl,
|
lineHeight: tokens.fontSize._2xl,
|
||||||
|
},
|
||||||
|
text_3xl: {
|
||||||
|
fontSize: tokens.fontSize._3xl,
|
||||||
|
lineHeight: tokens.fontSize._3xl,
|
||||||
|
},
|
||||||
|
text_4xl: {
|
||||||
|
fontSize: tokens.fontSize._4xl,
|
||||||
|
lineHeight: tokens.fontSize._4xl,
|
||||||
|
},
|
||||||
|
text_5xl: {
|
||||||
|
fontSize: tokens.fontSize._5xl,
|
||||||
|
lineHeight: tokens.fontSize._5xl,
|
||||||
},
|
},
|
||||||
leading_tight: {
|
leading_tight: {
|
||||||
lineHeight: 1.25,
|
lineHeight: 1.25,
|
||||||
@@ -162,11 +196,8 @@ export const atoms = {
|
|||||||
font_normal: {
|
font_normal: {
|
||||||
fontWeight: tokens.fontWeight.normal,
|
fontWeight: tokens.fontWeight.normal,
|
||||||
},
|
},
|
||||||
font_semibold: {
|
|
||||||
fontWeight: tokens.fontWeight.semibold,
|
|
||||||
},
|
|
||||||
font_bold: {
|
font_bold: {
|
||||||
fontWeight: tokens.fontWeight.bold,
|
fontWeight: tokens.fontWeight.semibold,
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -182,11 +213,30 @@ export const atoms = {
|
|||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shadow
|
||||||
|
*/
|
||||||
|
shadow_sm: {
|
||||||
|
shadowRadius: 8,
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
|
shadow_md: {
|
||||||
|
shadowRadius: 16,
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
elevation: 16,
|
||||||
|
},
|
||||||
|
shadow_lg: {
|
||||||
|
shadowRadius: 32,
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
elevation: 24,
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Padding
|
* Padding
|
||||||
*/
|
*/
|
||||||
p_xxs: {
|
p_2xs: {
|
||||||
padding: tokens.space.xxs,
|
padding: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
p_xs: {
|
p_xs: {
|
||||||
padding: tokens.space.xs,
|
padding: tokens.space.xs,
|
||||||
@@ -203,12 +253,21 @@ export const atoms = {
|
|||||||
p_xl: {
|
p_xl: {
|
||||||
padding: tokens.space.xl,
|
padding: tokens.space.xl,
|
||||||
},
|
},
|
||||||
p_xxl: {
|
p_2xl: {
|
||||||
padding: tokens.space.xxl,
|
padding: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
px_xxs: {
|
p_3xl: {
|
||||||
paddingLeft: tokens.space.xxs,
|
padding: tokens.space._3xl,
|
||||||
paddingRight: tokens.space.xxs,
|
},
|
||||||
|
p_4xl: {
|
||||||
|
padding: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
p_5xl: {
|
||||||
|
padding: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
px_2xs: {
|
||||||
|
paddingLeft: tokens.space._2xs,
|
||||||
|
paddingRight: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
px_xs: {
|
px_xs: {
|
||||||
paddingLeft: tokens.space.xs,
|
paddingLeft: tokens.space.xs,
|
||||||
@@ -230,13 +289,25 @@ export const atoms = {
|
|||||||
paddingLeft: tokens.space.xl,
|
paddingLeft: tokens.space.xl,
|
||||||
paddingRight: tokens.space.xl,
|
paddingRight: tokens.space.xl,
|
||||||
},
|
},
|
||||||
px_xxl: {
|
px_2xl: {
|
||||||
paddingLeft: tokens.space.xxl,
|
paddingLeft: tokens.space._2xl,
|
||||||
paddingRight: tokens.space.xxl,
|
paddingRight: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
py_xxs: {
|
px_3xl: {
|
||||||
paddingTop: tokens.space.xxs,
|
paddingLeft: tokens.space._3xl,
|
||||||
paddingBottom: tokens.space.xxs,
|
paddingRight: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
px_4xl: {
|
||||||
|
paddingLeft: tokens.space._4xl,
|
||||||
|
paddingRight: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
px_5xl: {
|
||||||
|
paddingLeft: tokens.space._5xl,
|
||||||
|
paddingRight: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
py_2xs: {
|
||||||
|
paddingTop: tokens.space._2xs,
|
||||||
|
paddingBottom: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
py_xs: {
|
py_xs: {
|
||||||
paddingTop: tokens.space.xs,
|
paddingTop: tokens.space.xs,
|
||||||
@@ -258,12 +329,24 @@ export const atoms = {
|
|||||||
paddingTop: tokens.space.xl,
|
paddingTop: tokens.space.xl,
|
||||||
paddingBottom: tokens.space.xl,
|
paddingBottom: tokens.space.xl,
|
||||||
},
|
},
|
||||||
py_xxl: {
|
py_2xl: {
|
||||||
paddingTop: tokens.space.xxl,
|
paddingTop: tokens.space._2xl,
|
||||||
paddingBottom: tokens.space.xxl,
|
paddingBottom: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
pt_xxs: {
|
py_3xl: {
|
||||||
paddingTop: tokens.space.xxs,
|
paddingTop: tokens.space._3xl,
|
||||||
|
paddingBottom: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
py_4xl: {
|
||||||
|
paddingTop: tokens.space._4xl,
|
||||||
|
paddingBottom: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
py_5xl: {
|
||||||
|
paddingTop: tokens.space._5xl,
|
||||||
|
paddingBottom: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pt_2xs: {
|
||||||
|
paddingTop: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
pt_xs: {
|
pt_xs: {
|
||||||
paddingTop: tokens.space.xs,
|
paddingTop: tokens.space.xs,
|
||||||
@@ -280,11 +363,20 @@ export const atoms = {
|
|||||||
pt_xl: {
|
pt_xl: {
|
||||||
paddingTop: tokens.space.xl,
|
paddingTop: tokens.space.xl,
|
||||||
},
|
},
|
||||||
pt_xxl: {
|
pt_2xl: {
|
||||||
paddingTop: tokens.space.xxl,
|
paddingTop: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
pb_xxs: {
|
pt_3xl: {
|
||||||
paddingBottom: tokens.space.xxs,
|
paddingTop: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pt_4xl: {
|
||||||
|
paddingTop: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pt_5xl: {
|
||||||
|
paddingTop: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pb_2xs: {
|
||||||
|
paddingBottom: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
pb_xs: {
|
pb_xs: {
|
||||||
paddingBottom: tokens.space.xs,
|
paddingBottom: tokens.space.xs,
|
||||||
@@ -301,11 +393,20 @@ export const atoms = {
|
|||||||
pb_xl: {
|
pb_xl: {
|
||||||
paddingBottom: tokens.space.xl,
|
paddingBottom: tokens.space.xl,
|
||||||
},
|
},
|
||||||
pb_xxl: {
|
pb_2xl: {
|
||||||
paddingBottom: tokens.space.xxl,
|
paddingBottom: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
pl_xxs: {
|
pb_3xl: {
|
||||||
paddingLeft: tokens.space.xxs,
|
paddingBottom: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pb_4xl: {
|
||||||
|
paddingBottom: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pb_5xl: {
|
||||||
|
paddingBottom: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pl_2xs: {
|
||||||
|
paddingLeft: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
pl_xs: {
|
pl_xs: {
|
||||||
paddingLeft: tokens.space.xs,
|
paddingLeft: tokens.space.xs,
|
||||||
@@ -322,11 +423,20 @@ export const atoms = {
|
|||||||
pl_xl: {
|
pl_xl: {
|
||||||
paddingLeft: tokens.space.xl,
|
paddingLeft: tokens.space.xl,
|
||||||
},
|
},
|
||||||
pl_xxl: {
|
pl_2xl: {
|
||||||
paddingLeft: tokens.space.xxl,
|
paddingLeft: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
pr_xxs: {
|
pl_3xl: {
|
||||||
paddingRight: tokens.space.xxs,
|
paddingLeft: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pl_4xl: {
|
||||||
|
paddingLeft: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pl_5xl: {
|
||||||
|
paddingLeft: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
pr_2xs: {
|
||||||
|
paddingRight: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
pr_xs: {
|
pr_xs: {
|
||||||
paddingRight: tokens.space.xs,
|
paddingRight: tokens.space.xs,
|
||||||
@@ -343,15 +453,24 @@ export const atoms = {
|
|||||||
pr_xl: {
|
pr_xl: {
|
||||||
paddingRight: tokens.space.xl,
|
paddingRight: tokens.space.xl,
|
||||||
},
|
},
|
||||||
pr_xxl: {
|
pr_2xl: {
|
||||||
paddingRight: tokens.space.xxl,
|
paddingRight: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
pr_3xl: {
|
||||||
|
paddingRight: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
pr_4xl: {
|
||||||
|
paddingRight: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
pr_5xl: {
|
||||||
|
paddingRight: tokens.space._5xl,
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Margin
|
* Margin
|
||||||
*/
|
*/
|
||||||
m_xxs: {
|
m_2xs: {
|
||||||
margin: tokens.space.xxs,
|
margin: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
m_xs: {
|
m_xs: {
|
||||||
margin: tokens.space.xs,
|
margin: tokens.space.xs,
|
||||||
@@ -368,12 +487,21 @@ export const atoms = {
|
|||||||
m_xl: {
|
m_xl: {
|
||||||
margin: tokens.space.xl,
|
margin: tokens.space.xl,
|
||||||
},
|
},
|
||||||
m_xxl: {
|
m_2xl: {
|
||||||
margin: tokens.space.xxl,
|
margin: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
mx_xxs: {
|
m_3xl: {
|
||||||
marginLeft: tokens.space.xxs,
|
margin: tokens.space._3xl,
|
||||||
marginRight: tokens.space.xxs,
|
},
|
||||||
|
m_4xl: {
|
||||||
|
margin: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
m_5xl: {
|
||||||
|
margin: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
mx_2xs: {
|
||||||
|
marginLeft: tokens.space._2xs,
|
||||||
|
marginRight: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
mx_xs: {
|
mx_xs: {
|
||||||
marginLeft: tokens.space.xs,
|
marginLeft: tokens.space.xs,
|
||||||
@@ -395,13 +523,25 @@ export const atoms = {
|
|||||||
marginLeft: tokens.space.xl,
|
marginLeft: tokens.space.xl,
|
||||||
marginRight: tokens.space.xl,
|
marginRight: tokens.space.xl,
|
||||||
},
|
},
|
||||||
mx_xxl: {
|
mx_2xl: {
|
||||||
marginLeft: tokens.space.xxl,
|
marginLeft: tokens.space._2xl,
|
||||||
marginRight: tokens.space.xxl,
|
marginRight: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
my_xxs: {
|
mx_3xl: {
|
||||||
marginTop: tokens.space.xxs,
|
marginLeft: tokens.space._3xl,
|
||||||
marginBottom: tokens.space.xxs,
|
marginRight: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
mx_4xl: {
|
||||||
|
marginLeft: tokens.space._4xl,
|
||||||
|
marginRight: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
mx_5xl: {
|
||||||
|
marginLeft: tokens.space._5xl,
|
||||||
|
marginRight: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
my_2xs: {
|
||||||
|
marginTop: tokens.space._2xs,
|
||||||
|
marginBottom: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
my_xs: {
|
my_xs: {
|
||||||
marginTop: tokens.space.xs,
|
marginTop: tokens.space.xs,
|
||||||
@@ -423,12 +563,24 @@ export const atoms = {
|
|||||||
marginTop: tokens.space.xl,
|
marginTop: tokens.space.xl,
|
||||||
marginBottom: tokens.space.xl,
|
marginBottom: tokens.space.xl,
|
||||||
},
|
},
|
||||||
my_xxl: {
|
my_2xl: {
|
||||||
marginTop: tokens.space.xxl,
|
marginTop: tokens.space._2xl,
|
||||||
marginBottom: tokens.space.xxl,
|
marginBottom: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
mt_xxs: {
|
my_3xl: {
|
||||||
marginTop: tokens.space.xxs,
|
marginTop: tokens.space._3xl,
|
||||||
|
marginBottom: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
my_4xl: {
|
||||||
|
marginTop: tokens.space._4xl,
|
||||||
|
marginBottom: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
my_5xl: {
|
||||||
|
marginTop: tokens.space._5xl,
|
||||||
|
marginBottom: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
mt_2xs: {
|
||||||
|
marginTop: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
mt_xs: {
|
mt_xs: {
|
||||||
marginTop: tokens.space.xs,
|
marginTop: tokens.space.xs,
|
||||||
@@ -445,11 +597,20 @@ export const atoms = {
|
|||||||
mt_xl: {
|
mt_xl: {
|
||||||
marginTop: tokens.space.xl,
|
marginTop: tokens.space.xl,
|
||||||
},
|
},
|
||||||
mt_xxl: {
|
mt_2xl: {
|
||||||
marginTop: tokens.space.xxl,
|
marginTop: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
mb_xxs: {
|
mt_3xl: {
|
||||||
marginBottom: tokens.space.xxs,
|
marginTop: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
mt_4xl: {
|
||||||
|
marginTop: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
mt_5xl: {
|
||||||
|
marginTop: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
mb_2xs: {
|
||||||
|
marginBottom: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
mb_xs: {
|
mb_xs: {
|
||||||
marginBottom: tokens.space.xs,
|
marginBottom: tokens.space.xs,
|
||||||
@@ -466,11 +627,20 @@ export const atoms = {
|
|||||||
mb_xl: {
|
mb_xl: {
|
||||||
marginBottom: tokens.space.xl,
|
marginBottom: tokens.space.xl,
|
||||||
},
|
},
|
||||||
mb_xxl: {
|
mb_2xl: {
|
||||||
marginBottom: tokens.space.xxl,
|
marginBottom: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
ml_xxs: {
|
mb_3xl: {
|
||||||
marginLeft: tokens.space.xxs,
|
marginBottom: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
mb_4xl: {
|
||||||
|
marginBottom: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
mb_5xl: {
|
||||||
|
marginBottom: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
ml_2xs: {
|
||||||
|
marginLeft: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
ml_xs: {
|
ml_xs: {
|
||||||
marginLeft: tokens.space.xs,
|
marginLeft: tokens.space.xs,
|
||||||
@@ -487,11 +657,20 @@ export const atoms = {
|
|||||||
ml_xl: {
|
ml_xl: {
|
||||||
marginLeft: tokens.space.xl,
|
marginLeft: tokens.space.xl,
|
||||||
},
|
},
|
||||||
ml_xxl: {
|
ml_2xl: {
|
||||||
marginLeft: tokens.space.xxl,
|
marginLeft: tokens.space._2xl,
|
||||||
},
|
},
|
||||||
mr_xxs: {
|
ml_3xl: {
|
||||||
marginRight: tokens.space.xxs,
|
marginLeft: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
ml_4xl: {
|
||||||
|
marginLeft: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
ml_5xl: {
|
||||||
|
marginLeft: tokens.space._5xl,
|
||||||
|
},
|
||||||
|
mr_2xs: {
|
||||||
|
marginRight: tokens.space._2xs,
|
||||||
},
|
},
|
||||||
mr_xs: {
|
mr_xs: {
|
||||||
marginRight: tokens.space.xs,
|
marginRight: tokens.space.xs,
|
||||||
@@ -508,7 +687,16 @@ export const atoms = {
|
|||||||
mr_xl: {
|
mr_xl: {
|
||||||
marginRight: tokens.space.xl,
|
marginRight: tokens.space.xl,
|
||||||
},
|
},
|
||||||
mr_xxl: {
|
mr_2xl: {
|
||||||
marginRight: tokens.space.xxl,
|
marginRight: tokens.space._2xl,
|
||||||
|
},
|
||||||
|
mr_3xl: {
|
||||||
|
marginRight: tokens.space._3xl,
|
||||||
|
},
|
||||||
|
mr_4xl: {
|
||||||
|
marginRight: tokens.space._4xl,
|
||||||
|
},
|
||||||
|
mr_5xl: {
|
||||||
|
marginRight: tokens.space._5xl,
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as themes from '#/alf/themes'
|
|||||||
export * as tokens from '#/alf/tokens'
|
export * as tokens from '#/alf/tokens'
|
||||||
export {atoms} from '#/alf/atoms'
|
export {atoms} from '#/alf/atoms'
|
||||||
export * from '#/alf/util/platform'
|
export * from '#/alf/util/platform'
|
||||||
|
export * from '#/alf/util/flatten'
|
||||||
|
|
||||||
type BreakpointName = keyof typeof breakpoints
|
type BreakpointName = keyof typeof breakpoints
|
||||||
|
|
||||||
|
|||||||
+259
-47
@@ -1,108 +1,320 @@
|
|||||||
import * as tokens from '#/alf/tokens'
|
import * as tokens from '#/alf/tokens'
|
||||||
import type {Mutable} from '#/alf/types'
|
import type {Mutable} from '#/alf/types'
|
||||||
|
import {atoms} from '#/alf/atoms'
|
||||||
|
|
||||||
export type ThemeName = 'light' | 'dark'
|
export type ThemeName = 'light' | 'dim' | 'dark'
|
||||||
export type ReadonlyTheme = typeof light
|
export type ReadonlyTheme = typeof light
|
||||||
export type Theme = Mutable<ReadonlyTheme>
|
export type Theme = Mutable<ReadonlyTheme>
|
||||||
|
export type ReadonlyPalette = typeof lightPalette
|
||||||
|
export type Palette = Mutable<ReadonlyPalette>
|
||||||
|
|
||||||
export type Palette = {
|
export const lightPalette = {
|
||||||
primary: string
|
white: tokens.color.gray_0,
|
||||||
positive: string
|
black: tokens.color.gray_1000,
|
||||||
negative: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const lightPalette: Palette = {
|
contrast_25: tokens.color.gray_25,
|
||||||
primary: tokens.color.blue_500,
|
contrast_50: tokens.color.gray_50,
|
||||||
positive: tokens.color.green_500,
|
contrast_100: tokens.color.gray_100,
|
||||||
negative: tokens.color.red_500,
|
contrast_200: tokens.color.gray_200,
|
||||||
|
contrast_300: tokens.color.gray_300,
|
||||||
|
contrast_400: tokens.color.gray_400,
|
||||||
|
contrast_500: tokens.color.gray_500,
|
||||||
|
contrast_600: tokens.color.gray_600,
|
||||||
|
contrast_700: tokens.color.gray_700,
|
||||||
|
contrast_800: tokens.color.gray_800,
|
||||||
|
contrast_900: tokens.color.gray_900,
|
||||||
|
contrast_950: tokens.color.gray_950,
|
||||||
|
contrast_975: tokens.color.gray_975,
|
||||||
|
|
||||||
|
primary_25: tokens.color.blue_25,
|
||||||
|
primary_50: tokens.color.blue_50,
|
||||||
|
primary_100: tokens.color.blue_100,
|
||||||
|
primary_200: tokens.color.blue_200,
|
||||||
|
primary_300: tokens.color.blue_300,
|
||||||
|
primary_400: tokens.color.blue_400,
|
||||||
|
primary_500: tokens.color.blue_500,
|
||||||
|
primary_600: tokens.color.blue_600,
|
||||||
|
primary_700: tokens.color.blue_700,
|
||||||
|
primary_800: tokens.color.blue_800,
|
||||||
|
primary_900: tokens.color.blue_900,
|
||||||
|
primary_950: tokens.color.blue_950,
|
||||||
|
primary_975: tokens.color.blue_975,
|
||||||
|
|
||||||
|
positive_25: tokens.color.green_25,
|
||||||
|
positive_50: tokens.color.green_50,
|
||||||
|
positive_100: tokens.color.green_100,
|
||||||
|
positive_200: tokens.color.green_200,
|
||||||
|
positive_300: tokens.color.green_300,
|
||||||
|
positive_400: tokens.color.green_400,
|
||||||
|
positive_500: tokens.color.green_500,
|
||||||
|
positive_600: tokens.color.green_600,
|
||||||
|
positive_700: tokens.color.green_700,
|
||||||
|
positive_800: tokens.color.green_800,
|
||||||
|
positive_900: tokens.color.green_900,
|
||||||
|
positive_950: tokens.color.green_950,
|
||||||
|
positive_975: tokens.color.green_975,
|
||||||
|
|
||||||
|
negative_25: tokens.color.red_25,
|
||||||
|
negative_50: tokens.color.red_50,
|
||||||
|
negative_100: tokens.color.red_100,
|
||||||
|
negative_200: tokens.color.red_200,
|
||||||
|
negative_300: tokens.color.red_300,
|
||||||
|
negative_400: tokens.color.red_400,
|
||||||
|
negative_500: tokens.color.red_500,
|
||||||
|
negative_600: tokens.color.red_600,
|
||||||
|
negative_700: tokens.color.red_700,
|
||||||
|
negative_800: tokens.color.red_800,
|
||||||
|
negative_900: tokens.color.red_900,
|
||||||
|
negative_950: tokens.color.red_950,
|
||||||
|
negative_975: tokens.color.red_975,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const darkPalette: Palette = {
|
export const darkPalette: Palette = {
|
||||||
primary: tokens.color.blue_500,
|
white: tokens.color.gray_0,
|
||||||
positive: tokens.color.green_400,
|
black: tokens.color.gray_1000,
|
||||||
negative: tokens.color.red_400,
|
|
||||||
|
contrast_25: tokens.color.gray_975,
|
||||||
|
contrast_50: tokens.color.gray_950,
|
||||||
|
contrast_100: tokens.color.gray_900,
|
||||||
|
contrast_200: tokens.color.gray_800,
|
||||||
|
contrast_300: tokens.color.gray_700,
|
||||||
|
contrast_400: tokens.color.gray_600,
|
||||||
|
contrast_500: tokens.color.gray_500,
|
||||||
|
contrast_600: tokens.color.gray_400,
|
||||||
|
contrast_700: tokens.color.gray_300,
|
||||||
|
contrast_800: tokens.color.gray_200,
|
||||||
|
contrast_900: tokens.color.gray_100,
|
||||||
|
contrast_950: tokens.color.gray_50,
|
||||||
|
contrast_975: tokens.color.gray_25,
|
||||||
|
|
||||||
|
primary_25: tokens.color.blue_25,
|
||||||
|
primary_50: tokens.color.blue_50,
|
||||||
|
primary_100: tokens.color.blue_100,
|
||||||
|
primary_200: tokens.color.blue_200,
|
||||||
|
primary_300: tokens.color.blue_300,
|
||||||
|
primary_400: tokens.color.blue_400,
|
||||||
|
primary_500: tokens.color.blue_500,
|
||||||
|
primary_600: tokens.color.blue_600,
|
||||||
|
primary_700: tokens.color.blue_700,
|
||||||
|
primary_800: tokens.color.blue_800,
|
||||||
|
primary_900: tokens.color.blue_900,
|
||||||
|
primary_950: tokens.color.blue_950,
|
||||||
|
primary_975: tokens.color.blue_975,
|
||||||
|
|
||||||
|
positive_25: tokens.color.green_25,
|
||||||
|
positive_50: tokens.color.green_50,
|
||||||
|
positive_100: tokens.color.green_100,
|
||||||
|
positive_200: tokens.color.green_200,
|
||||||
|
positive_300: tokens.color.green_300,
|
||||||
|
positive_400: tokens.color.green_400,
|
||||||
|
positive_500: tokens.color.green_500,
|
||||||
|
positive_600: tokens.color.green_600,
|
||||||
|
positive_700: tokens.color.green_700,
|
||||||
|
positive_800: tokens.color.green_800,
|
||||||
|
positive_900: tokens.color.green_900,
|
||||||
|
positive_950: tokens.color.green_950,
|
||||||
|
positive_975: tokens.color.green_975,
|
||||||
|
|
||||||
|
negative_25: tokens.color.red_25,
|
||||||
|
negative_50: tokens.color.red_50,
|
||||||
|
negative_100: tokens.color.red_100,
|
||||||
|
negative_200: tokens.color.red_200,
|
||||||
|
negative_300: tokens.color.red_300,
|
||||||
|
negative_400: tokens.color.red_400,
|
||||||
|
negative_500: tokens.color.red_500,
|
||||||
|
negative_600: tokens.color.red_600,
|
||||||
|
negative_700: tokens.color.red_700,
|
||||||
|
negative_800: tokens.color.red_800,
|
||||||
|
negative_900: tokens.color.red_900,
|
||||||
|
negative_950: tokens.color.red_950,
|
||||||
|
negative_975: tokens.color.red_975,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const light = {
|
export const light = {
|
||||||
|
name: 'light',
|
||||||
palette: lightPalette,
|
palette: lightPalette,
|
||||||
atoms: {
|
atoms: {
|
||||||
text: {
|
text: {
|
||||||
color: tokens.color.gray_1000,
|
color: lightPalette.black,
|
||||||
},
|
},
|
||||||
text_contrast_700: {
|
text_contrast_700: {
|
||||||
color: tokens.color.gray_700,
|
color: lightPalette.contrast_700,
|
||||||
|
},
|
||||||
|
text_contrast_600: {
|
||||||
|
color: lightPalette.contrast_600,
|
||||||
},
|
},
|
||||||
text_contrast_500: {
|
text_contrast_500: {
|
||||||
color: tokens.color.gray_500,
|
color: lightPalette.contrast_500,
|
||||||
|
},
|
||||||
|
text_contrast_400: {
|
||||||
|
color: lightPalette.contrast_400,
|
||||||
},
|
},
|
||||||
text_inverted: {
|
text_inverted: {
|
||||||
color: tokens.color.white,
|
color: lightPalette.white,
|
||||||
},
|
},
|
||||||
bg: {
|
bg: {
|
||||||
backgroundColor: tokens.color.white,
|
backgroundColor: lightPalette.white,
|
||||||
|
},
|
||||||
|
bg_contrast_25: {
|
||||||
|
backgroundColor: lightPalette.contrast_25,
|
||||||
|
},
|
||||||
|
bg_contrast_50: {
|
||||||
|
backgroundColor: lightPalette.contrast_50,
|
||||||
},
|
},
|
||||||
bg_contrast_100: {
|
bg_contrast_100: {
|
||||||
backgroundColor: tokens.color.gray_100,
|
backgroundColor: lightPalette.contrast_100,
|
||||||
},
|
},
|
||||||
bg_contrast_200: {
|
bg_contrast_200: {
|
||||||
backgroundColor: tokens.color.gray_200,
|
backgroundColor: lightPalette.contrast_200,
|
||||||
},
|
},
|
||||||
bg_contrast_300: {
|
bg_contrast_300: {
|
||||||
backgroundColor: tokens.color.gray_300,
|
backgroundColor: lightPalette.contrast_300,
|
||||||
},
|
|
||||||
bg_positive: {
|
|
||||||
backgroundColor: tokens.color.green_500,
|
|
||||||
},
|
|
||||||
bg_negative: {
|
|
||||||
backgroundColor: tokens.color.red_400,
|
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
borderColor: tokens.color.gray_200,
|
borderColor: lightPalette.contrast_100,
|
||||||
},
|
},
|
||||||
border_contrast_500: {
|
border_contrast: {
|
||||||
borderColor: tokens.color.gray_500,
|
borderColor: lightPalette.contrast_400,
|
||||||
|
},
|
||||||
|
shadow_sm: {
|
||||||
|
...atoms.shadow_sm,
|
||||||
|
shadowColor: lightPalette.black,
|
||||||
|
},
|
||||||
|
shadow_md: {
|
||||||
|
...atoms.shadow_md,
|
||||||
|
shadowColor: lightPalette.black,
|
||||||
|
},
|
||||||
|
shadow_lg: {
|
||||||
|
...atoms.shadow_lg,
|
||||||
|
shadowColor: lightPalette.black,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dim: Theme = {
|
||||||
|
name: 'dim',
|
||||||
|
palette: darkPalette,
|
||||||
|
atoms: {
|
||||||
|
text: {
|
||||||
|
color: darkPalette.white,
|
||||||
|
},
|
||||||
|
text_contrast_700: {
|
||||||
|
color: darkPalette.contrast_800,
|
||||||
|
},
|
||||||
|
text_contrast_600: {
|
||||||
|
color: darkPalette.contrast_700,
|
||||||
|
},
|
||||||
|
text_contrast_500: {
|
||||||
|
color: darkPalette.contrast_600,
|
||||||
|
},
|
||||||
|
text_contrast_400: {
|
||||||
|
color: darkPalette.contrast_500,
|
||||||
|
},
|
||||||
|
text_inverted: {
|
||||||
|
color: darkPalette.black,
|
||||||
|
},
|
||||||
|
bg: {
|
||||||
|
backgroundColor: darkPalette.contrast_50,
|
||||||
|
},
|
||||||
|
bg_contrast_25: {
|
||||||
|
backgroundColor: darkPalette.contrast_100,
|
||||||
|
},
|
||||||
|
bg_contrast_50: {
|
||||||
|
backgroundColor: darkPalette.contrast_200,
|
||||||
|
},
|
||||||
|
bg_contrast_100: {
|
||||||
|
backgroundColor: darkPalette.contrast_300,
|
||||||
|
},
|
||||||
|
bg_contrast_200: {
|
||||||
|
backgroundColor: darkPalette.contrast_400,
|
||||||
|
},
|
||||||
|
bg_contrast_300: {
|
||||||
|
backgroundColor: darkPalette.contrast_500,
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
borderColor: darkPalette.contrast_200,
|
||||||
|
},
|
||||||
|
border_contrast: {
|
||||||
|
borderColor: darkPalette.contrast_400,
|
||||||
|
},
|
||||||
|
shadow_sm: {
|
||||||
|
...atoms.shadow_sm,
|
||||||
|
shadowOpacity: 0.7,
|
||||||
|
shadowColor: tokens.color.trueBlack,
|
||||||
|
},
|
||||||
|
shadow_md: {
|
||||||
|
...atoms.shadow_md,
|
||||||
|
shadowOpacity: 0.7,
|
||||||
|
shadowColor: tokens.color.trueBlack,
|
||||||
|
},
|
||||||
|
shadow_lg: {
|
||||||
|
...atoms.shadow_lg,
|
||||||
|
shadowOpacity: 0.7,
|
||||||
|
shadowColor: tokens.color.trueBlack,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dark: Theme = {
|
export const dark: Theme = {
|
||||||
|
name: 'dark',
|
||||||
palette: darkPalette,
|
palette: darkPalette,
|
||||||
atoms: {
|
atoms: {
|
||||||
text: {
|
text: {
|
||||||
color: tokens.color.white,
|
color: darkPalette.white,
|
||||||
},
|
},
|
||||||
text_contrast_700: {
|
text_contrast_700: {
|
||||||
color: tokens.color.gray_300,
|
color: darkPalette.contrast_700,
|
||||||
|
},
|
||||||
|
text_contrast_600: {
|
||||||
|
color: darkPalette.contrast_600,
|
||||||
},
|
},
|
||||||
text_contrast_500: {
|
text_contrast_500: {
|
||||||
color: tokens.color.gray_500,
|
color: darkPalette.contrast_500,
|
||||||
|
},
|
||||||
|
text_contrast_400: {
|
||||||
|
color: darkPalette.contrast_400,
|
||||||
},
|
},
|
||||||
text_inverted: {
|
text_inverted: {
|
||||||
color: tokens.color.gray_1000,
|
color: darkPalette.black,
|
||||||
},
|
},
|
||||||
bg: {
|
bg: {
|
||||||
backgroundColor: tokens.color.gray_1000,
|
backgroundColor: darkPalette.black,
|
||||||
|
},
|
||||||
|
bg_contrast_25: {
|
||||||
|
backgroundColor: darkPalette.contrast_50,
|
||||||
|
},
|
||||||
|
bg_contrast_50: {
|
||||||
|
backgroundColor: darkPalette.contrast_100,
|
||||||
},
|
},
|
||||||
bg_contrast_100: {
|
bg_contrast_100: {
|
||||||
backgroundColor: tokens.color.gray_900,
|
backgroundColor: darkPalette.contrast_200,
|
||||||
},
|
},
|
||||||
bg_contrast_200: {
|
bg_contrast_200: {
|
||||||
backgroundColor: tokens.color.gray_800,
|
backgroundColor: darkPalette.contrast_300,
|
||||||
},
|
},
|
||||||
bg_contrast_300: {
|
bg_contrast_300: {
|
||||||
backgroundColor: tokens.color.gray_700,
|
backgroundColor: darkPalette.contrast_400,
|
||||||
},
|
|
||||||
bg_positive: {
|
|
||||||
backgroundColor: tokens.color.green_400,
|
|
||||||
},
|
|
||||||
bg_negative: {
|
|
||||||
backgroundColor: tokens.color.red_400,
|
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
borderColor: tokens.color.gray_800,
|
borderColor: darkPalette.contrast_100,
|
||||||
},
|
},
|
||||||
border_contrast_500: {
|
border_contrast: {
|
||||||
borderColor: tokens.color.gray_500,
|
borderColor: darkPalette.contrast_300,
|
||||||
|
},
|
||||||
|
shadow_sm: {
|
||||||
|
...atoms.shadow_sm,
|
||||||
|
shadowOpacity: 0.7,
|
||||||
|
shadowColor: tokens.color.trueBlack,
|
||||||
|
},
|
||||||
|
shadow_md: {
|
||||||
|
...atoms.shadow_md,
|
||||||
|
shadowOpacity: 0.7,
|
||||||
|
shadowColor: tokens.color.trueBlack,
|
||||||
|
},
|
||||||
|
shadow_lg: {
|
||||||
|
...atoms.shadow_lg,
|
||||||
|
shadowOpacity: 0.7,
|
||||||
|
shadowColor: tokens.color.trueBlack,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-53
@@ -1,79 +1,95 @@
|
|||||||
const BLUE_HUE = 211
|
const BLUE_HUE = 211
|
||||||
const GRAYSCALE_SATURATION = 22
|
const RED_HUE = 346
|
||||||
|
const GREEN_HUE = 152
|
||||||
|
|
||||||
export const color = {
|
export const color = {
|
||||||
white: '#FFFFFF',
|
trueBlack: '#000000',
|
||||||
|
|
||||||
gray_0: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 100%)`,
|
gray_0: `hsl(${BLUE_HUE}, 20%, 100%)`,
|
||||||
gray_100: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 95%)`,
|
gray_25: `hsl(${BLUE_HUE}, 20%, 97%)`,
|
||||||
gray_200: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 85%)`,
|
gray_50: `hsl(${BLUE_HUE}, 20%, 95%)`,
|
||||||
gray_300: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 75%)`,
|
gray_100: `hsl(${BLUE_HUE}, 20%, 90%)`,
|
||||||
gray_400: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 65%)`,
|
gray_200: `hsl(${BLUE_HUE}, 20%, 80%)`,
|
||||||
gray_500: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 55%)`,
|
gray_300: `hsl(${BLUE_HUE}, 20%, 70%)`,
|
||||||
gray_600: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 45%)`,
|
gray_400: `hsl(${BLUE_HUE}, 20%, 60%)`,
|
||||||
gray_700: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 35%)`,
|
gray_500: `hsl(${BLUE_HUE}, 20%, 50%)`,
|
||||||
gray_800: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 25%)`,
|
gray_600: `hsl(${BLUE_HUE}, 20%, 42%)`,
|
||||||
gray_900: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 15%)`,
|
gray_700: `hsl(${BLUE_HUE}, 20%, 34%)`,
|
||||||
gray_1000: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 5%)`,
|
gray_800: `hsl(${BLUE_HUE}, 20%, 26%)`,
|
||||||
|
gray_900: `hsl(${BLUE_HUE}, 20%, 18%)`,
|
||||||
|
gray_950: `hsl(${BLUE_HUE}, 20%, 10%)`,
|
||||||
|
gray_975: `hsl(${BLUE_HUE}, 20%, 7%)`,
|
||||||
|
gray_1000: `hsl(${BLUE_HUE}, 20%, 4%)`,
|
||||||
|
|
||||||
blue_0: `hsl(${BLUE_HUE}, 99%, 100%)`,
|
blue_25: `hsl(${BLUE_HUE}, 99%, 97%)`,
|
||||||
blue_100: `hsl(${BLUE_HUE}, 99%, 93%)`,
|
blue_50: `hsl(${BLUE_HUE}, 99%, 95%)`,
|
||||||
blue_200: `hsl(${BLUE_HUE}, 99%, 83%)`,
|
blue_100: `hsl(${BLUE_HUE}, 99%, 90%)`,
|
||||||
blue_300: `hsl(${BLUE_HUE}, 99%, 73%)`,
|
blue_200: `hsl(${BLUE_HUE}, 99%, 80%)`,
|
||||||
blue_400: `hsl(${BLUE_HUE}, 99%, 63%)`,
|
blue_300: `hsl(${BLUE_HUE}, 99%, 70%)`,
|
||||||
|
blue_400: `hsl(${BLUE_HUE}, 99%, 60%)`,
|
||||||
blue_500: `hsl(${BLUE_HUE}, 99%, 53%)`,
|
blue_500: `hsl(${BLUE_HUE}, 99%, 53%)`,
|
||||||
blue_600: `hsl(${BLUE_HUE}, 99%, 43%)`,
|
blue_600: `hsl(${BLUE_HUE}, 99%, 42%)`,
|
||||||
blue_700: `hsl(${BLUE_HUE}, 99%, 33%)`,
|
blue_700: `hsl(${BLUE_HUE}, 99%, 34%)`,
|
||||||
blue_800: `hsl(${BLUE_HUE}, 99%, 23%)`,
|
blue_800: `hsl(${BLUE_HUE}, 99%, 26%)`,
|
||||||
blue_900: `hsl(${BLUE_HUE}, 99%, 13%)`,
|
blue_900: `hsl(${BLUE_HUE}, 99%, 18%)`,
|
||||||
blue_1000: `hsl(${BLUE_HUE}, 99%, 8%)`,
|
blue_950: `hsl(${BLUE_HUE}, 99%, 10%)`,
|
||||||
|
blue_975: `hsl(${BLUE_HUE}, 99%, 7%)`,
|
||||||
|
|
||||||
green_0: `hsl(130, 60%, 100%)`,
|
green_25: `hsl(${GREEN_HUE}, 82%, 97%)`,
|
||||||
green_100: `hsl(130, 60%, 95%)`,
|
green_50: `hsl(${GREEN_HUE}, 82%, 95%)`,
|
||||||
green_200: `hsl(130, 60%, 85%)`,
|
green_100: `hsl(${GREEN_HUE}, 82%, 90%)`,
|
||||||
green_300: `hsl(130, 60%, 75%)`,
|
green_200: `hsl(${GREEN_HUE}, 82%, 80%)`,
|
||||||
green_400: `hsl(130, 60%, 65%)`,
|
green_300: `hsl(${GREEN_HUE}, 82%, 70%)`,
|
||||||
green_500: `hsl(130, 60%, 55%)`,
|
green_400: `hsl(${GREEN_HUE}, 82%, 60%)`,
|
||||||
green_600: `hsl(130, 60%, 45%)`,
|
green_500: `hsl(${GREEN_HUE}, 82%, 50%)`,
|
||||||
green_700: `hsl(130, 60%, 35%)`,
|
green_600: `hsl(${GREEN_HUE}, 82%, 42%)`,
|
||||||
green_800: `hsl(130, 60%, 25%)`,
|
green_700: `hsl(${GREEN_HUE}, 82%, 34%)`,
|
||||||
green_900: `hsl(130, 60%, 15%)`,
|
green_800: `hsl(${GREEN_HUE}, 82%, 26%)`,
|
||||||
green_1000: `hsl(130, 60%, 5%)`,
|
green_900: `hsl(${GREEN_HUE}, 82%, 18%)`,
|
||||||
|
green_950: `hsl(${GREEN_HUE}, 82%, 10%)`,
|
||||||
|
green_975: `hsl(${GREEN_HUE}, 82%, 7%)`,
|
||||||
|
|
||||||
red_0: `hsl(349, 96%, 100%)`,
|
red_25: `hsl(${RED_HUE}, 91%, 97%)`,
|
||||||
red_100: `hsl(349, 96%, 95%)`,
|
red_50: `hsl(${RED_HUE}, 91%, 95%)`,
|
||||||
red_200: `hsl(349, 96%, 85%)`,
|
red_100: `hsl(${RED_HUE}, 91%, 90%)`,
|
||||||
red_300: `hsl(349, 96%, 75%)`,
|
red_200: `hsl(${RED_HUE}, 91%, 80%)`,
|
||||||
red_400: `hsl(349, 96%, 65%)`,
|
red_300: `hsl(${RED_HUE}, 91%, 70%)`,
|
||||||
red_500: `hsl(349, 96%, 55%)`,
|
red_400: `hsl(${RED_HUE}, 91%, 60%)`,
|
||||||
red_600: `hsl(349, 96%, 45%)`,
|
red_500: `hsl(${RED_HUE}, 91%, 50%)`,
|
||||||
red_700: `hsl(349, 96%, 35%)`,
|
red_600: `hsl(${RED_HUE}, 91%, 42%)`,
|
||||||
red_800: `hsl(349, 96%, 25%)`,
|
red_700: `hsl(${RED_HUE}, 91%, 34%)`,
|
||||||
red_900: `hsl(349, 96%, 15%)`,
|
red_800: `hsl(${RED_HUE}, 91%, 26%)`,
|
||||||
red_1000: `hsl(349, 96%, 5%)`,
|
red_900: `hsl(${RED_HUE}, 91%, 18%)`,
|
||||||
|
red_950: `hsl(${RED_HUE}, 91%, 10%)`,
|
||||||
|
red_975: `hsl(${RED_HUE}, 91%, 7%)`,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const space = {
|
export const space = {
|
||||||
xxs: 2,
|
_2xs: 2,
|
||||||
xs: 4,
|
xs: 4,
|
||||||
sm: 8,
|
sm: 8,
|
||||||
md: 12,
|
md: 12,
|
||||||
lg: 18,
|
lg: 16,
|
||||||
xl: 24,
|
xl: 20,
|
||||||
xxl: 32,
|
_2xl: 24,
|
||||||
|
_3xl: 28,
|
||||||
|
_4xl: 32,
|
||||||
|
_5xl: 40,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const fontSize = {
|
export const fontSize = {
|
||||||
xxs: 10,
|
_2xs: 10,
|
||||||
xs: 12,
|
xs: 12,
|
||||||
sm: 14,
|
sm: 14,
|
||||||
md: 16,
|
md: 16,
|
||||||
lg: 18,
|
lg: 18,
|
||||||
xl: 22,
|
xl: 20,
|
||||||
xxl: 26,
|
_2xl: 22,
|
||||||
|
_3xl: 26,
|
||||||
|
_4xl: 32,
|
||||||
|
_5xl: 40,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// TODO test
|
|
||||||
export const lineHeight = {
|
export const lineHeight = {
|
||||||
none: 1,
|
none: 1,
|
||||||
normal: 1.5,
|
normal: 1.5,
|
||||||
@@ -81,6 +97,8 @@ export const lineHeight = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const borderRadius = {
|
export const borderRadius = {
|
||||||
|
_2xs: 2,
|
||||||
|
xs: 4,
|
||||||
sm: 8,
|
sm: 8,
|
||||||
md: 12,
|
md: 12,
|
||||||
full: 999,
|
full: 999,
|
||||||
@@ -92,6 +110,56 @@ export const fontWeight = {
|
|||||||
bold: '900',
|
bold: '900',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
export const gradients = {
|
||||||
|
sky: {
|
||||||
|
values: [
|
||||||
|
[0, '#0A7AFF'],
|
||||||
|
[1, '#59B9FF'],
|
||||||
|
],
|
||||||
|
hover_value: '#0A7AFF',
|
||||||
|
},
|
||||||
|
midnight: {
|
||||||
|
values: [
|
||||||
|
[0, '#022C5E'],
|
||||||
|
[1, '#4079BC'],
|
||||||
|
],
|
||||||
|
hover_value: '#022C5E',
|
||||||
|
},
|
||||||
|
sunrise: {
|
||||||
|
values: [
|
||||||
|
[0, '#4E90AE'],
|
||||||
|
[0.4, '#AEA3AB'],
|
||||||
|
[0.8, '#E6A98F'],
|
||||||
|
[1, '#F3A84C'],
|
||||||
|
],
|
||||||
|
hover_value: '#AEA3AB',
|
||||||
|
},
|
||||||
|
sunset: {
|
||||||
|
values: [
|
||||||
|
[0, '#6772AF'],
|
||||||
|
[0.6, '#B88BB6'],
|
||||||
|
[1, '#FFA6AC'],
|
||||||
|
],
|
||||||
|
hover_value: '#B88BB6',
|
||||||
|
},
|
||||||
|
nordic: {
|
||||||
|
values: [
|
||||||
|
[0, '#083367'],
|
||||||
|
[1, '#9EE8C1'],
|
||||||
|
],
|
||||||
|
hover_value: '#3A7085',
|
||||||
|
},
|
||||||
|
bonfire: {
|
||||||
|
values: [
|
||||||
|
[0, '#203E4E'],
|
||||||
|
[0.4, '#755B62'],
|
||||||
|
[0.8, '#CD7765'],
|
||||||
|
[1, '#EF956E'],
|
||||||
|
],
|
||||||
|
hover_value: '#755B62',
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
export type Color = keyof typeof color
|
export type Color = keyof typeof color
|
||||||
export type Space = keyof typeof space
|
export type Space = keyof typeof space
|
||||||
export type FontSize = keyof typeof fontSize
|
export type FontSize = keyof typeof fontSize
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import {StyleSheet} from 'react-native'
|
||||||
|
|
||||||
|
export const flatten = StyleSheet.flatten
|
||||||
@@ -0,0 +1,507 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
Pressable,
|
||||||
|
Text,
|
||||||
|
PressableProps,
|
||||||
|
TextProps,
|
||||||
|
ViewStyle,
|
||||||
|
AccessibilityProps,
|
||||||
|
View,
|
||||||
|
TextStyle,
|
||||||
|
StyleSheet,
|
||||||
|
} from 'react-native'
|
||||||
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
|
|
||||||
|
import {useTheme, atoms as a, tokens, web, native} from '#/alf'
|
||||||
|
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||||
|
|
||||||
|
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
|
||||||
|
export type ButtonColor =
|
||||||
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
|
| 'negative'
|
||||||
|
| 'gradient_sky'
|
||||||
|
| 'gradient_midnight'
|
||||||
|
| 'gradient_sunrise'
|
||||||
|
| 'gradient_sunset'
|
||||||
|
| 'gradient_nordic'
|
||||||
|
| 'gradient_bonfire'
|
||||||
|
export type ButtonSize = 'small' | 'large'
|
||||||
|
export type VariantProps = {
|
||||||
|
/**
|
||||||
|
* The style variation of the button
|
||||||
|
*/
|
||||||
|
variant?: ButtonVariant
|
||||||
|
/**
|
||||||
|
* The color of the button
|
||||||
|
*/
|
||||||
|
color?: ButtonColor
|
||||||
|
/**
|
||||||
|
* The size of the button
|
||||||
|
*/
|
||||||
|
size?: ButtonSize
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ButtonProps = React.PropsWithChildren<
|
||||||
|
Pick<PressableProps, 'disabled' | 'onPress'> &
|
||||||
|
AccessibilityProps &
|
||||||
|
VariantProps & {
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
|
||||||
|
|
||||||
|
const Context = React.createContext<
|
||||||
|
VariantProps & {
|
||||||
|
hovered: boolean
|
||||||
|
focused: boolean
|
||||||
|
pressed: boolean
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
hovered: false,
|
||||||
|
focused: false,
|
||||||
|
pressed: false,
|
||||||
|
disabled: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useButtonContext() {
|
||||||
|
return React.useContext(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
children,
|
||||||
|
variant,
|
||||||
|
color,
|
||||||
|
size,
|
||||||
|
label,
|
||||||
|
disabled = false,
|
||||||
|
...rest
|
||||||
|
}: ButtonProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
const [state, setState] = React.useState({
|
||||||
|
pressed: false,
|
||||||
|
hovered: false,
|
||||||
|
focused: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onPressIn = React.useCallback(() => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
pressed: true,
|
||||||
|
}))
|
||||||
|
}, [setState])
|
||||||
|
const onPressOut = React.useCallback(() => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
pressed: false,
|
||||||
|
}))
|
||||||
|
}, [setState])
|
||||||
|
const onHoverIn = React.useCallback(() => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
hovered: true,
|
||||||
|
}))
|
||||||
|
}, [setState])
|
||||||
|
const onHoverOut = React.useCallback(() => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
hovered: false,
|
||||||
|
}))
|
||||||
|
}, [setState])
|
||||||
|
const onFocus = React.useCallback(() => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
focused: true,
|
||||||
|
}))
|
||||||
|
}, [setState])
|
||||||
|
const onBlur = React.useCallback(() => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
focused: false,
|
||||||
|
}))
|
||||||
|
}, [setState])
|
||||||
|
|
||||||
|
const {baseStyles, hoverStyles, focusStyles} = React.useMemo(() => {
|
||||||
|
const baseStyles: ViewStyle[] = []
|
||||||
|
const hoverStyles: ViewStyle[] = []
|
||||||
|
const light = t.name === 'light'
|
||||||
|
|
||||||
|
if (color === 'primary') {
|
||||||
|
if (variant === 'solid') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({
|
||||||
|
backgroundColor: t.palette.primary_500,
|
||||||
|
})
|
||||||
|
hoverStyles.push({
|
||||||
|
backgroundColor: t.palette.primary_600,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({
|
||||||
|
backgroundColor: t.palette.primary_700,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'outline') {
|
||||||
|
baseStyles.push(a.border, t.atoms.bg, {
|
||||||
|
borderWidth: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push(a.border, {
|
||||||
|
borderColor: tokens.color.blue_500,
|
||||||
|
})
|
||||||
|
hoverStyles.push(a.border, {
|
||||||
|
backgroundColor: light
|
||||||
|
? t.palette.primary_50
|
||||||
|
: t.palette.primary_950,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push(a.border, {
|
||||||
|
borderColor: light ? tokens.color.blue_200 : tokens.color.blue_900,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'ghost') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push(t.atoms.bg)
|
||||||
|
hoverStyles.push({
|
||||||
|
backgroundColor: light
|
||||||
|
? t.palette.primary_100
|
||||||
|
: t.palette.primary_900,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (color === 'secondary') {
|
||||||
|
if (variant === 'solid') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({
|
||||||
|
backgroundColor: light
|
||||||
|
? tokens.color.gray_100
|
||||||
|
: tokens.color.gray_900,
|
||||||
|
})
|
||||||
|
hoverStyles.push({
|
||||||
|
backgroundColor: light
|
||||||
|
? tokens.color.gray_200
|
||||||
|
: tokens.color.gray_950,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({
|
||||||
|
backgroundColor: light
|
||||||
|
? tokens.color.gray_300
|
||||||
|
: tokens.color.gray_950,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'outline') {
|
||||||
|
baseStyles.push(a.border, t.atoms.bg, {
|
||||||
|
borderWidth: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push(a.border, {
|
||||||
|
borderColor: light ? tokens.color.gray_500 : tokens.color.gray_500,
|
||||||
|
})
|
||||||
|
hoverStyles.push(a.border, t.atoms.bg_contrast_50)
|
||||||
|
} else {
|
||||||
|
baseStyles.push(a.border, {
|
||||||
|
borderColor: light ? tokens.color.gray_200 : tokens.color.gray_800,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'ghost') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push(t.atoms.bg)
|
||||||
|
hoverStyles.push({
|
||||||
|
backgroundColor: light
|
||||||
|
? tokens.color.gray_100
|
||||||
|
: tokens.color.gray_900,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (color === 'negative') {
|
||||||
|
if (variant === 'solid') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({
|
||||||
|
backgroundColor: t.palette.negative_400,
|
||||||
|
})
|
||||||
|
hoverStyles.push({
|
||||||
|
backgroundColor: t.palette.negative_500,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({
|
||||||
|
backgroundColor: t.palette.negative_600,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'outline') {
|
||||||
|
baseStyles.push(a.border, t.atoms.bg, {
|
||||||
|
borderWidth: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push(a.border, {
|
||||||
|
borderColor: t.palette.negative_400,
|
||||||
|
})
|
||||||
|
hoverStyles.push(a.border, {
|
||||||
|
backgroundColor: light
|
||||||
|
? t.palette.negative_50
|
||||||
|
: t.palette.negative_975,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push(a.border, {
|
||||||
|
borderColor: light
|
||||||
|
? t.palette.negative_200
|
||||||
|
: t.palette.negative_900,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'ghost') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push(t.atoms.bg)
|
||||||
|
hoverStyles.push({
|
||||||
|
backgroundColor: light
|
||||||
|
? t.palette.negative_100
|
||||||
|
: t.palette.negative_950,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size === 'large') {
|
||||||
|
baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_sm)
|
||||||
|
} else if (size === 'small') {
|
||||||
|
baseStyles.push({paddingVertical: 9}, a.px_md, a.rounded_sm, a.gap_sm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseStyles,
|
||||||
|
hoverStyles,
|
||||||
|
focusStyles: [
|
||||||
|
...hoverStyles,
|
||||||
|
{
|
||||||
|
outline: 0,
|
||||||
|
} as ViewStyle,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}, [t, variant, color, size, disabled])
|
||||||
|
|
||||||
|
const {gradientColors, gradientHoverColors, gradientLocations} =
|
||||||
|
React.useMemo(() => {
|
||||||
|
const colors: string[] = []
|
||||||
|
const hoverColors: string[] = []
|
||||||
|
const locations: number[] = []
|
||||||
|
const gradient = {
|
||||||
|
primary: tokens.gradients.sky,
|
||||||
|
secondary: tokens.gradients.sky,
|
||||||
|
negative: tokens.gradients.sky,
|
||||||
|
gradient_sky: tokens.gradients.sky,
|
||||||
|
gradient_midnight: tokens.gradients.midnight,
|
||||||
|
gradient_sunrise: tokens.gradients.sunrise,
|
||||||
|
gradient_sunset: tokens.gradients.sunset,
|
||||||
|
gradient_nordic: tokens.gradients.nordic,
|
||||||
|
gradient_bonfire: tokens.gradients.bonfire,
|
||||||
|
}[color || 'primary']
|
||||||
|
|
||||||
|
if (variant === 'gradient') {
|
||||||
|
colors.push(...gradient.values.map(([_, color]) => color))
|
||||||
|
hoverColors.push(...gradient.values.map(_ => gradient.hover_value))
|
||||||
|
locations.push(...gradient.values.map(([location, _]) => location))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
gradientColors: colors,
|
||||||
|
gradientHoverColors: hoverColors,
|
||||||
|
gradientLocations: locations,
|
||||||
|
}
|
||||||
|
}, [variant, color])
|
||||||
|
|
||||||
|
const context = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
...state,
|
||||||
|
variant,
|
||||||
|
color,
|
||||||
|
size,
|
||||||
|
disabled: disabled || false,
|
||||||
|
}),
|
||||||
|
[state, variant, color, size, disabled],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
role="button"
|
||||||
|
accessibilityHint={undefined} // optional
|
||||||
|
{...rest}
|
||||||
|
aria-label={label}
|
||||||
|
aria-pressed={state.pressed}
|
||||||
|
accessibilityLabel={label}
|
||||||
|
disabled={disabled || false}
|
||||||
|
accessibilityState={{
|
||||||
|
disabled: disabled || false,
|
||||||
|
}}
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.overflow_hidden,
|
||||||
|
...baseStyles,
|
||||||
|
...(state.hovered || state.pressed ? hoverStyles : []),
|
||||||
|
...(state.focused ? focusStyles : []),
|
||||||
|
]}
|
||||||
|
onPressIn={onPressIn}
|
||||||
|
onPressOut={onPressOut}
|
||||||
|
onHoverIn={onHoverIn}
|
||||||
|
onHoverOut={onHoverOut}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}>
|
||||||
|
{variant === 'gradient' && (
|
||||||
|
<LinearGradient
|
||||||
|
colors={
|
||||||
|
state.hovered || state.pressed || state.focused
|
||||||
|
? gradientHoverColors
|
||||||
|
: gradientColors
|
||||||
|
}
|
||||||
|
locations={gradientLocations}
|
||||||
|
start={{x: 0, y: 0}}
|
||||||
|
end={{x: 1, y: 1}}
|
||||||
|
style={[a.absolute, a.inset_0]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Context.Provider value={context}>
|
||||||
|
{typeof children === 'string' ? (
|
||||||
|
<ButtonText>{children}</ButtonText>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</Context.Provider>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSharedButtonTextStyles() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {color, variant, disabled, size} = useButtonContext()
|
||||||
|
return React.useMemo(() => {
|
||||||
|
const baseStyles: TextStyle[] = []
|
||||||
|
const light = t.name === 'light'
|
||||||
|
|
||||||
|
if (color === 'primary') {
|
||||||
|
if (variant === 'solid') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({color: t.palette.white})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({color: t.palette.white, opacity: 0.5})
|
||||||
|
}
|
||||||
|
} else if (variant === 'outline') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({
|
||||||
|
color: light ? t.palette.primary_600 : t.palette.primary_500,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
|
||||||
|
}
|
||||||
|
} else if (variant === 'ghost') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({color: t.palette.primary_600})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (color === 'secondary') {
|
||||||
|
if (variant === 'solid' || variant === 'gradient') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({
|
||||||
|
color: light ? tokens.color.gray_700 : tokens.color.gray_100,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({
|
||||||
|
color: light ? tokens.color.gray_400 : tokens.color.gray_700,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'outline') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({
|
||||||
|
color: light ? tokens.color.gray_600 : tokens.color.gray_300,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({
|
||||||
|
color: light ? tokens.color.gray_400 : tokens.color.gray_700,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (variant === 'ghost') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({
|
||||||
|
color: light ? tokens.color.gray_600 : tokens.color.gray_300,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({
|
||||||
|
color: light ? tokens.color.gray_400 : tokens.color.gray_600,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (color === 'negative') {
|
||||||
|
if (variant === 'solid' || variant === 'gradient') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({color: t.palette.white})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({color: t.palette.white, opacity: 0.5})
|
||||||
|
}
|
||||||
|
} else if (variant === 'outline') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({color: t.palette.negative_400})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
|
||||||
|
}
|
||||||
|
} else if (variant === 'ghost') {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({color: t.palette.negative_400})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!disabled) {
|
||||||
|
baseStyles.push({color: t.palette.white})
|
||||||
|
} else {
|
||||||
|
baseStyles.push({color: t.palette.white, opacity: 0.5})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size === 'large') {
|
||||||
|
baseStyles.push(
|
||||||
|
a.text_md,
|
||||||
|
web({paddingBottom: 1}),
|
||||||
|
native({marginTop: 2}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
baseStyles.push(
|
||||||
|
a.text_md,
|
||||||
|
web({paddingBottom: 1}),
|
||||||
|
native({marginTop: 2}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return StyleSheet.flatten(baseStyles)
|
||||||
|
}, [t, variant, color, size, disabled])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonText({children, style, ...rest}: ButtonTextProps) {
|
||||||
|
const textStyles = useSharedButtonTextStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text {...rest} style={[a.font_bold, a.text_center, textStyles, style]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonIcon({
|
||||||
|
icon: Comp,
|
||||||
|
}: {
|
||||||
|
icon: React.ComponentType<SVGIconProps>
|
||||||
|
}) {
|
||||||
|
const {size} = useButtonContext()
|
||||||
|
const textStyles = useSharedButtonTextStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.z_20]}>
|
||||||
|
<Comp
|
||||||
|
size={size === 'large' ? 'md' : 'sm'}
|
||||||
|
style={[{color: textStyles.color, pointerEvents: 'none'}]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {useDialogStateContext} from '#/state/dialogs'
|
||||||
|
import {DialogContextProps, DialogControlProps} from '#/components/Dialog/types'
|
||||||
|
|
||||||
|
export const Context = React.createContext<DialogContextProps>({
|
||||||
|
close: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useDialogContext() {
|
||||||
|
return React.useContext(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDialogControl() {
|
||||||
|
const id = React.useId()
|
||||||
|
const control = React.useRef<DialogControlProps>({
|
||||||
|
open: () => {},
|
||||||
|
close: () => {},
|
||||||
|
})
|
||||||
|
const {activeDialogs} = useDialogStateContext()
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
activeDialogs.current.set(id, control)
|
||||||
|
return () => {
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
activeDialogs.current.delete(id)
|
||||||
|
}
|
||||||
|
}, [id, activeDialogs])
|
||||||
|
|
||||||
|
return {
|
||||||
|
ref: control,
|
||||||
|
open: () => control.current.open(),
|
||||||
|
close: () => control.current.close(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import React, {useImperativeHandle} from 'react'
|
||||||
|
import {View, Dimensions} from 'react-native'
|
||||||
|
import BottomSheet, {
|
||||||
|
BottomSheetBackdrop,
|
||||||
|
BottomSheetScrollView,
|
||||||
|
BottomSheetTextInput,
|
||||||
|
BottomSheetView,
|
||||||
|
} from '@gorhom/bottom-sheet'
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
|
|
||||||
|
import {useTheme, atoms as a} from '#/alf'
|
||||||
|
import {Portal} from '#/components/Portal'
|
||||||
|
import {createInput} from '#/components/forms/TextField'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DialogOuterProps,
|
||||||
|
DialogControlProps,
|
||||||
|
DialogInnerProps,
|
||||||
|
} from '#/components/Dialog/types'
|
||||||
|
import {Context} from '#/components/Dialog/context'
|
||||||
|
|
||||||
|
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
|
||||||
|
export * from '#/components/Dialog/types'
|
||||||
|
// @ts-ignore
|
||||||
|
export const Input = createInput(BottomSheetTextInput)
|
||||||
|
|
||||||
|
export function Outer({
|
||||||
|
children,
|
||||||
|
control,
|
||||||
|
onClose,
|
||||||
|
nativeOptions,
|
||||||
|
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const sheet = React.useRef<BottomSheet>(null)
|
||||||
|
const sheetOptions = nativeOptions?.sheet || {}
|
||||||
|
const hasSnapPoints = !!sheetOptions.snapPoints
|
||||||
|
|
||||||
|
const open = React.useCallback<DialogControlProps['open']>((i = 0) => {
|
||||||
|
sheet.current?.snapToIndex(i)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const close = React.useCallback(() => {
|
||||||
|
sheet.current?.close()
|
||||||
|
onClose?.()
|
||||||
|
}, [onClose])
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
control.ref,
|
||||||
|
() => ({
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
}),
|
||||||
|
[open, close],
|
||||||
|
)
|
||||||
|
|
||||||
|
const context = React.useMemo(() => ({close}), [close])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<BottomSheet
|
||||||
|
enableDynamicSizing={!hasSnapPoints}
|
||||||
|
enablePanDownToClose
|
||||||
|
keyboardBehavior="interactive"
|
||||||
|
android_keyboardInputMode="adjustResize"
|
||||||
|
keyboardBlurBehavior="restore"
|
||||||
|
{...sheetOptions}
|
||||||
|
ref={sheet}
|
||||||
|
index={-1}
|
||||||
|
backgroundStyle={{backgroundColor: 'transparent'}}
|
||||||
|
backdropComponent={props => (
|
||||||
|
<BottomSheetBackdrop
|
||||||
|
opacity={0.4}
|
||||||
|
appearsOnIndex={0}
|
||||||
|
disappearsOnIndex={-1}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
|
||||||
|
handleStyle={{display: 'none'}}
|
||||||
|
onClose={onClose}>
|
||||||
|
<Context.Provider value={context}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
t.atoms.bg,
|
||||||
|
{
|
||||||
|
borderTopLeftRadius: 40,
|
||||||
|
borderTopRightRadius: 40,
|
||||||
|
height: Dimensions.get('window').height * 2,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
</BottomSheet>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO a11y props here, or is that handled by the sheet?
|
||||||
|
export function Inner(props: DialogInnerProps) {
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
|
return (
|
||||||
|
<BottomSheetView
|
||||||
|
style={[
|
||||||
|
a.p_lg,
|
||||||
|
a.pt_3xl,
|
||||||
|
{
|
||||||
|
borderTopLeftRadius: 40,
|
||||||
|
borderTopRightRadius: 40,
|
||||||
|
paddingBottom: insets.bottom + a.pb_5xl.paddingBottom,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{props.children}
|
||||||
|
</BottomSheetView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScrollableInner(props: DialogInnerProps) {
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
|
return (
|
||||||
|
<BottomSheetScrollView
|
||||||
|
style={[
|
||||||
|
a.flex_1, // main diff is this
|
||||||
|
a.p_lg,
|
||||||
|
a.pt_3xl,
|
||||||
|
{
|
||||||
|
borderTopLeftRadius: 40,
|
||||||
|
borderTopRightRadius: 40,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{props.children}
|
||||||
|
<View style={{height: insets.bottom + a.pt_5xl.paddingTop}} />
|
||||||
|
</BottomSheetScrollView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Handle() {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.z_10,
|
||||||
|
{
|
||||||
|
top: a.pt_lg.paddingTop,
|
||||||
|
width: 35,
|
||||||
|
height: 4,
|
||||||
|
alignSelf: 'center',
|
||||||
|
backgroundColor: t.palette.contrast_900,
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Close() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import React, {useImperativeHandle} from 'react'
|
||||||
|
import {View, TouchableWithoutFeedback} from 'react-native'
|
||||||
|
import {FocusScope} from '@tamagui/focus-scope'
|
||||||
|
import Animated, {FadeInDown, FadeIn} from 'react-native-reanimated'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useTheme, atoms as a, useBreakpoints, web} from '#/alf'
|
||||||
|
import {Portal} from '#/components/Portal'
|
||||||
|
|
||||||
|
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
|
||||||
|
import {Context} from '#/components/Dialog/context'
|
||||||
|
|
||||||
|
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
|
||||||
|
export * from '#/components/Dialog/types'
|
||||||
|
export {Input} from '#/components/forms/TextField'
|
||||||
|
|
||||||
|
const stopPropagation = (e: any) => e.stopPropagation()
|
||||||
|
|
||||||
|
export function Outer({
|
||||||
|
control,
|
||||||
|
onClose,
|
||||||
|
children,
|
||||||
|
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false)
|
||||||
|
const [isVisible, setIsVisible] = React.useState(true)
|
||||||
|
|
||||||
|
const open = React.useCallback(() => {
|
||||||
|
setIsOpen(true)
|
||||||
|
}, [setIsOpen])
|
||||||
|
|
||||||
|
const close = React.useCallback(async () => {
|
||||||
|
setIsVisible(false)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 150))
|
||||||
|
setIsOpen(false)
|
||||||
|
setIsVisible(true)
|
||||||
|
onClose?.()
|
||||||
|
}, [onClose, setIsOpen])
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
control.ref,
|
||||||
|
() => ({
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
}),
|
||||||
|
[open, close],
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isOpen) return
|
||||||
|
|
||||||
|
function handler(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') close()
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handler)
|
||||||
|
|
||||||
|
return () => document.removeEventListener('keydown', handler)
|
||||||
|
}, [isOpen, close])
|
||||||
|
|
||||||
|
const context = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
close,
|
||||||
|
}),
|
||||||
|
[close],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isOpen && (
|
||||||
|
<Portal>
|
||||||
|
<Context.Provider value={context}>
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
accessibilityHint={undefined}
|
||||||
|
accessibilityLabel={_(msg`Close active dialog`)}
|
||||||
|
onPress={close}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
web(a.fixed),
|
||||||
|
a.inset_0,
|
||||||
|
a.z_10,
|
||||||
|
a.align_center,
|
||||||
|
gtMobile ? a.p_lg : a.p_md,
|
||||||
|
{overflowY: 'auto'},
|
||||||
|
]}>
|
||||||
|
{isVisible && (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeIn.duration(150)}
|
||||||
|
// exiting={FadeOut.duration(150)}
|
||||||
|
style={[
|
||||||
|
web(a.fixed),
|
||||||
|
a.inset_0,
|
||||||
|
{opacity: 0.5, backgroundColor: t.palette.black},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.z_20,
|
||||||
|
a.justify_center,
|
||||||
|
a.align_center,
|
||||||
|
{
|
||||||
|
minHeight: web('calc(90vh - 36px)') || undefined,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{isVisible ? children : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</Context.Provider>
|
||||||
|
</Portal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Inner({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
label,
|
||||||
|
accessibilityLabelledBy,
|
||||||
|
accessibilityDescribedBy,
|
||||||
|
}: DialogInnerProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
return (
|
||||||
|
<FocusScope loop enabled trapped>
|
||||||
|
<Animated.View
|
||||||
|
role="dialog"
|
||||||
|
aria-role="dialog"
|
||||||
|
aria-label={label}
|
||||||
|
aria-labelledby={accessibilityLabelledBy}
|
||||||
|
aria-describedby={accessibilityDescribedBy}
|
||||||
|
// @ts-ignore web only -prf
|
||||||
|
onClick={stopPropagation}
|
||||||
|
onStartShouldSetResponder={_ => true}
|
||||||
|
onTouchEnd={stopPropagation}
|
||||||
|
entering={FadeInDown.duration(100)}
|
||||||
|
// exiting={FadeOut.duration(100)}
|
||||||
|
style={[
|
||||||
|
a.relative,
|
||||||
|
a.rounded_md,
|
||||||
|
a.w_full,
|
||||||
|
a.border,
|
||||||
|
gtMobile ? a.p_xl : a.p_lg,
|
||||||
|
t.atoms.bg,
|
||||||
|
{
|
||||||
|
maxWidth: 600,
|
||||||
|
borderColor: t.palette.contrast_200,
|
||||||
|
shadowColor: t.palette.black,
|
||||||
|
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
||||||
|
shadowRadius: 30,
|
||||||
|
},
|
||||||
|
...(Array.isArray(style) ? style : [style || {}]),
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Animated.View>
|
||||||
|
</FocusScope>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScrollableInner = Inner
|
||||||
|
|
||||||
|
export function Handle() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO(eric) unused rn
|
||||||
|
*/
|
||||||
|
// export function Close() {
|
||||||
|
// const {_} = useLingui()
|
||||||
|
// const t = useTheme()
|
||||||
|
// const {close} = useDialogContext()
|
||||||
|
// return (
|
||||||
|
// <View
|
||||||
|
// style={[
|
||||||
|
// a.absolute,
|
||||||
|
// a.z_10,
|
||||||
|
// {
|
||||||
|
// top: a.pt_lg.paddingTop,
|
||||||
|
// right: a.pr_lg.paddingRight,
|
||||||
|
// },
|
||||||
|
// ]}>
|
||||||
|
// <Button onPress={close} label={_(msg`Close active dialog`)}>
|
||||||
|
// </Button>
|
||||||
|
// </View>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import type {ViewStyle, AccessibilityProps} from 'react-native'
|
||||||
|
import {BottomSheetProps} from '@gorhom/bottom-sheet'
|
||||||
|
|
||||||
|
type A11yProps = Required<AccessibilityProps>
|
||||||
|
|
||||||
|
export type DialogContextProps = {
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogControlProps = {
|
||||||
|
open: (index?: number) => void
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogOuterProps = {
|
||||||
|
control: {
|
||||||
|
ref: React.RefObject<DialogControlProps>
|
||||||
|
open: (index?: number) => void
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
|
onClose?: () => void
|
||||||
|
nativeOptions?: {
|
||||||
|
sheet?: Omit<BottomSheetProps, 'children'>
|
||||||
|
}
|
||||||
|
webOptions?: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialogInnerPropsBase<T> = React.PropsWithChildren<{
|
||||||
|
style?: ViewStyle
|
||||||
|
}> &
|
||||||
|
T
|
||||||
|
export type DialogInnerProps =
|
||||||
|
| DialogInnerPropsBase<{
|
||||||
|
label?: undefined
|
||||||
|
accessibilityLabelledBy: A11yProps['aria-labelledby']
|
||||||
|
accessibilityDescribedBy: string
|
||||||
|
}>
|
||||||
|
| DialogInnerPropsBase<{
|
||||||
|
label: string
|
||||||
|
accessibilityLabelledBy?: undefined
|
||||||
|
accessibilityDescribedBy?: undefined
|
||||||
|
}>
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
Text,
|
||||||
|
TextStyle,
|
||||||
|
StyleProp,
|
||||||
|
GestureResponderEvent,
|
||||||
|
Linking,
|
||||||
|
} from 'react-native'
|
||||||
|
import {
|
||||||
|
useLinkProps,
|
||||||
|
useNavigation,
|
||||||
|
StackActions,
|
||||||
|
} from '@react-navigation/native'
|
||||||
|
import {sanitizeUrl} from '@braintree/sanitize-url'
|
||||||
|
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
|
import {useTheme, web, flatten} from '#/alf'
|
||||||
|
import {Button, ButtonProps, useButtonContext} from '#/components/Button'
|
||||||
|
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||||
|
import {
|
||||||
|
convertBskyAppUrlIfNeeded,
|
||||||
|
isExternalUrl,
|
||||||
|
linkRequiresWarning,
|
||||||
|
} from '#/lib/strings/url-helpers'
|
||||||
|
import {useModalControls} from '#/state/modals'
|
||||||
|
import {router} from '#/routes'
|
||||||
|
|
||||||
|
export type LinkProps = Omit<
|
||||||
|
ButtonProps,
|
||||||
|
'style' | 'onPress' | 'disabled' | 'label'
|
||||||
|
> & {
|
||||||
|
/**
|
||||||
|
* `TextStyle` to apply to the anchor element itself. Does not apply to any children.
|
||||||
|
*/
|
||||||
|
style?: StyleProp<TextStyle>
|
||||||
|
/**
|
||||||
|
* The React Navigation `StackAction` to perform when the link is pressed.
|
||||||
|
*/
|
||||||
|
action?: 'push' | 'replace' | 'navigate'
|
||||||
|
/**
|
||||||
|
* If true, will warn the user if the link text does not match the href. Only
|
||||||
|
* works for Links with children that are strings i.e. text links.
|
||||||
|
*/
|
||||||
|
warnOnMismatchingTextChild?: boolean
|
||||||
|
label?: ButtonProps['label']
|
||||||
|
} & Pick<Parameters<typeof useLinkProps<AllNavigatorParams>>[0], 'to'>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A interactive element that renders as a `<a>` tag on the web. On mobile it
|
||||||
|
* will translate the `href` to navigator screens and params and dispatch a
|
||||||
|
* navigation action.
|
||||||
|
*
|
||||||
|
* Intended to behave as a web anchor tag. For more complex routing, use a
|
||||||
|
* `Button`.
|
||||||
|
*/
|
||||||
|
export function Link({
|
||||||
|
children,
|
||||||
|
to,
|
||||||
|
action = 'push',
|
||||||
|
warnOnMismatchingTextChild,
|
||||||
|
style,
|
||||||
|
...rest
|
||||||
|
}: LinkProps) {
|
||||||
|
const navigation = useNavigation<NavigationProp>()
|
||||||
|
const {href} = useLinkProps<AllNavigatorParams>({
|
||||||
|
to:
|
||||||
|
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
|
||||||
|
})
|
||||||
|
const isExternal = isExternalUrl(href)
|
||||||
|
const {openModal, closeModal} = useModalControls()
|
||||||
|
const onPress = React.useCallback(
|
||||||
|
(e: GestureResponderEvent) => {
|
||||||
|
const stringChildren = typeof children === 'string' ? children : ''
|
||||||
|
const requiresWarning = Boolean(
|
||||||
|
warnOnMismatchingTextChild &&
|
||||||
|
stringChildren &&
|
||||||
|
isExternal &&
|
||||||
|
linkRequiresWarning(href, stringChildren),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (requiresWarning) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
name: 'link-warning',
|
||||||
|
text: stringChildren,
|
||||||
|
href: href,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (isExternal) {
|
||||||
|
Linking.openURL(href)
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
|
||||||
|
* of @ts-ignore below.
|
||||||
|
*/
|
||||||
|
const event = e as any
|
||||||
|
const isMiddleClick = isWeb && event.button === 1
|
||||||
|
const isMetaKey =
|
||||||
|
isWeb &&
|
||||||
|
(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
|
||||||
|
const shouldOpenInNewTab = isMetaKey || isMiddleClick
|
||||||
|
|
||||||
|
if (
|
||||||
|
shouldOpenInNewTab ||
|
||||||
|
href.startsWith('http') ||
|
||||||
|
href.startsWith('mailto')
|
||||||
|
) {
|
||||||
|
Linking.openURL(href)
|
||||||
|
} else {
|
||||||
|
closeModal() // close any active modals
|
||||||
|
|
||||||
|
if (action === 'push') {
|
||||||
|
navigation.dispatch(StackActions.push(...router.matchPath(href)))
|
||||||
|
} else if (action === 'replace') {
|
||||||
|
navigation.dispatch(
|
||||||
|
StackActions.replace(...router.matchPath(href)),
|
||||||
|
)
|
||||||
|
} else if (action === 'navigate') {
|
||||||
|
// @ts-ignore
|
||||||
|
navigation.navigate(...router.matchPath(href))
|
||||||
|
} else {
|
||||||
|
throw Error('Unsupported navigator action.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
href,
|
||||||
|
isExternal,
|
||||||
|
warnOnMismatchingTextChild,
|
||||||
|
navigation,
|
||||||
|
action,
|
||||||
|
children,
|
||||||
|
closeModal,
|
||||||
|
openModal,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
label={href}
|
||||||
|
{...rest}
|
||||||
|
role="link"
|
||||||
|
accessibilityRole="link"
|
||||||
|
href={href}
|
||||||
|
onPress={onPress}
|
||||||
|
{...web({
|
||||||
|
hrefAttrs: {
|
||||||
|
target: isExternal ? 'blank' : undefined,
|
||||||
|
rel: isExternal ? 'noopener noreferrer' : undefined,
|
||||||
|
},
|
||||||
|
dataSet: {
|
||||||
|
// default to no underline, apply this ourselves
|
||||||
|
noUnderline: '1',
|
||||||
|
},
|
||||||
|
})}>
|
||||||
|
{typeof children === 'string' ? (
|
||||||
|
<LinkText style={style}>{children}</LinkText>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinkText({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
}: React.PropsWithChildren<{
|
||||||
|
style?: StyleProp<TextStyle>
|
||||||
|
}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {hovered} = useButtonContext()
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
{color: t.palette.primary_500},
|
||||||
|
hovered && {
|
||||||
|
textDecorationLine: 'underline',
|
||||||
|
textDecorationColor: t.palette.primary_500,
|
||||||
|
},
|
||||||
|
flatten(style),
|
||||||
|
]}>
|
||||||
|
{children as string}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Component = React.ReactElement
|
||||||
|
|
||||||
|
type ContextType = {
|
||||||
|
outlet: Component | null
|
||||||
|
append(id: string, component: Component): void
|
||||||
|
remove(id: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentMap = {
|
||||||
|
[id: string]: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Context = React.createContext<ContextType>({
|
||||||
|
outlet: null,
|
||||||
|
append: () => {},
|
||||||
|
remove: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Provider(props: React.PropsWithChildren<{}>) {
|
||||||
|
const map = React.useRef<ComponentMap>({})
|
||||||
|
const [outlet, setOutlet] = React.useState<ContextType['outlet']>(null)
|
||||||
|
|
||||||
|
const append = React.useCallback<ContextType['append']>((id, component) => {
|
||||||
|
if (map.current[id]) return
|
||||||
|
map.current[id] = <React.Fragment key={id}>{component}</React.Fragment>
|
||||||
|
setOutlet(<>{Object.values(map.current)}</>)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const remove = React.useCallback<ContextType['remove']>(id => {
|
||||||
|
delete map.current[id]
|
||||||
|
setOutlet(<>{Object.values(map.current)}</>)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={{outlet, append, remove}}>
|
||||||
|
{props.children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Outlet() {
|
||||||
|
const ctx = React.useContext(Context)
|
||||||
|
return ctx.outlet
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Portal({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const {append, remove} = React.useContext(Context)
|
||||||
|
const id = React.useId()
|
||||||
|
React.useEffect(() => {
|
||||||
|
append(id, children as Component)
|
||||||
|
return () => remove(id)
|
||||||
|
}, [id, children, append, remove])
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View, PressableProps} from 'react-native'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useTheme, atoms as a} from '#/alf'
|
||||||
|
import {H4, P} from '#/components/Typography'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
|
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
|
||||||
|
export {useDialogControl as usePromptControl} from '#/components/Dialog'
|
||||||
|
|
||||||
|
const Context = React.createContext<{
|
||||||
|
titleId: string
|
||||||
|
descriptionId: string
|
||||||
|
}>({
|
||||||
|
titleId: '',
|
||||||
|
descriptionId: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Outer({
|
||||||
|
children,
|
||||||
|
control,
|
||||||
|
}: React.PropsWithChildren<{
|
||||||
|
control: Dialog.DialogOuterProps['control']
|
||||||
|
}>) {
|
||||||
|
const titleId = React.useId()
|
||||||
|
const descriptionId = React.useId()
|
||||||
|
|
||||||
|
const context = React.useMemo(
|
||||||
|
() => ({titleId, descriptionId}),
|
||||||
|
[titleId, descriptionId],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control}>
|
||||||
|
<Context.Provider value={context}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
|
||||||
|
<Dialog.Inner
|
||||||
|
accessibilityLabelledBy={titleId}
|
||||||
|
accessibilityDescribedBy={descriptionId}
|
||||||
|
style={{width: 'auto', maxWidth: 400}}>
|
||||||
|
{children}
|
||||||
|
</Dialog.Inner>
|
||||||
|
</Context.Provider>
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Title({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {titleId} = React.useContext(Context)
|
||||||
|
return (
|
||||||
|
<H4
|
||||||
|
nativeID={titleId}
|
||||||
|
style={[a.font_bold, t.atoms.text_contrast_700, a.pb_sm]}>
|
||||||
|
{children}
|
||||||
|
</H4>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Description({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {descriptionId} = React.useContext(Context)
|
||||||
|
return (
|
||||||
|
<P nativeID={descriptionId} style={[t.atoms.text, a.pb_lg]}>
|
||||||
|
{children}
|
||||||
|
</P>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Actions({children}: React.PropsWithChildren<{}>) {
|
||||||
|
return (
|
||||||
|
<View style={[a.w_full, a.flex_row, a.gap_sm, a.justify_end]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Cancel({
|
||||||
|
children,
|
||||||
|
}: React.PropsWithChildren<{onPress?: PressableProps['onPress']}>) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {close} = Dialog.useDialogContext()
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
label={_(msg`Cancel`)}
|
||||||
|
onPress={close}>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Action({
|
||||||
|
children,
|
||||||
|
onPress,
|
||||||
|
}: React.PropsWithChildren<{onPress?: () => void}>) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {close} = Dialog.useDialogContext()
|
||||||
|
const handleOnPress = React.useCallback(() => {
|
||||||
|
close()
|
||||||
|
onPress?.()
|
||||||
|
}, [close, onPress])
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
label={_(msg`Confirm`)}
|
||||||
|
onPress={handleOnPress}>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Text as RNText, TextProps} from 'react-native'
|
import {Text as RNText, TextProps} from 'react-native'
|
||||||
import {useTheme, atoms, web} from '#/alf'
|
|
||||||
|
import {useTheme, atoms, web, flatten} from '#/alf'
|
||||||
|
|
||||||
export function Text({style, ...rest}: TextProps) {
|
export function Text({style, ...rest}: TextProps) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -18,7 +19,7 @@ export function H1({style, ...rest}: TextProps) {
|
|||||||
<RNText
|
<RNText
|
||||||
{...attr}
|
{...attr}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[atoms.text_xl, atoms.font_bold, t.atoms.text, style]}
|
style={[atoms.text_5xl, atoms.font_bold, t.atoms.text, flatten(style)]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -34,7 +35,7 @@ export function H2({style, ...rest}: TextProps) {
|
|||||||
<RNText
|
<RNText
|
||||||
{...attr}
|
{...attr}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[atoms.text_lg, atoms.font_bold, t.atoms.text, style]}
|
style={[atoms.text_4xl, atoms.font_bold, t.atoms.text, flatten(style)]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ export function H3({style, ...rest}: TextProps) {
|
|||||||
<RNText
|
<RNText
|
||||||
{...attr}
|
{...attr}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[atoms.text_md, atoms.font_bold, t.atoms.text, style]}
|
style={[atoms.text_3xl, atoms.font_bold, t.atoms.text, flatten(style)]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ export function H4({style, ...rest}: TextProps) {
|
|||||||
<RNText
|
<RNText
|
||||||
{...attr}
|
{...attr}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[atoms.text_sm, atoms.font_bold, t.atoms.text, style]}
|
style={[atoms.text_2xl, atoms.font_bold, t.atoms.text, flatten(style)]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ export function H5({style, ...rest}: TextProps) {
|
|||||||
<RNText
|
<RNText
|
||||||
{...attr}
|
{...attr}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[atoms.text_xs, atoms.font_bold, t.atoms.text, style]}
|
style={[atoms.text_xl, atoms.font_bold, t.atoms.text, flatten(style)]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -98,7 +99,26 @@ export function H6({style, ...rest}: TextProps) {
|
|||||||
<RNText
|
<RNText
|
||||||
{...attr}
|
{...attr}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[atoms.text_xxs, atoms.font_bold, t.atoms.text, style]}
|
style={[atoms.text_lg, atoms.font_bold, t.atoms.text, flatten(style)]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function P({style, ...rest}: TextProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
const attr =
|
||||||
|
web({
|
||||||
|
role: 'paragraph',
|
||||||
|
}) || {}
|
||||||
|
const _style = flatten(style)
|
||||||
|
const lineHeight =
|
||||||
|
(_style?.lineHeight || atoms.text_md.lineHeight) *
|
||||||
|
atoms.leading_normal.lineHeight
|
||||||
|
return (
|
||||||
|
<RNText
|
||||||
|
{...attr}
|
||||||
|
{...rest}
|
||||||
|
style={[atoms.text_md, t.atoms.text, _style, {lineHeight}]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View, Pressable} from 'react-native'
|
||||||
|
import DateTimePicker, {
|
||||||
|
BaseProps as DateTimePickerProps,
|
||||||
|
} from '@react-native-community/datetimepicker'
|
||||||
|
|
||||||
|
import {useTheme, atoms} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
|
||||||
|
|
||||||
|
import {DateFieldProps} from '#/components/forms/DateField/types'
|
||||||
|
import {
|
||||||
|
localizeDate,
|
||||||
|
toSimpleDateString,
|
||||||
|
} from '#/components/forms/DateField/utils'
|
||||||
|
|
||||||
|
export * as utils from '#/components/forms/DateField/utils'
|
||||||
|
export const Label = TextField.Label
|
||||||
|
|
||||||
|
export function DateField({
|
||||||
|
value,
|
||||||
|
onChangeDate,
|
||||||
|
label,
|
||||||
|
isInvalid,
|
||||||
|
testID,
|
||||||
|
}: DateFieldProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const {
|
||||||
|
state: pressed,
|
||||||
|
onIn: onPressIn,
|
||||||
|
onOut: onPressOut,
|
||||||
|
} = useInteractionState()
|
||||||
|
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||||
|
|
||||||
|
const {chromeFocus, chromeError, chromeErrorHover} =
|
||||||
|
TextField.useSharedInputStyles()
|
||||||
|
|
||||||
|
const onChangeInternal = React.useCallback<
|
||||||
|
Required<DateTimePickerProps>['onChange']
|
||||||
|
>(
|
||||||
|
(_event, date) => {
|
||||||
|
setOpen(false)
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
const formatted = toSimpleDateString(date)
|
||||||
|
onChangeDate(formatted)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChangeDate, setOpen],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[atoms.relative, atoms.w_full]}>
|
||||||
|
<Pressable
|
||||||
|
aria-label={label}
|
||||||
|
accessibilityLabel={label}
|
||||||
|
accessibilityHint={undefined}
|
||||||
|
onPress={() => setOpen(true)}
|
||||||
|
onPressIn={onPressIn}
|
||||||
|
onPressOut={onPressOut}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
paddingTop: 16,
|
||||||
|
paddingBottom: 16,
|
||||||
|
borderColor: 'transparent',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
atoms.flex_row,
|
||||||
|
atoms.flex_1,
|
||||||
|
atoms.w_full,
|
||||||
|
atoms.px_lg,
|
||||||
|
atoms.rounded_sm,
|
||||||
|
t.atoms.bg_contrast_50,
|
||||||
|
focused || pressed ? chromeFocus : {},
|
||||||
|
isInvalid ? chromeError : {},
|
||||||
|
isInvalid && (focused || pressed) ? chromeErrorHover : {},
|
||||||
|
]}>
|
||||||
|
<TextField.Icon icon={CalendarDays} />
|
||||||
|
|
||||||
|
<Text
|
||||||
|
style={[atoms.text_md, atoms.pl_xs, t.atoms.text, {paddingTop: 3}]}>
|
||||||
|
{localizeDate(value)}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<DateTimePicker
|
||||||
|
aria-label={label}
|
||||||
|
accessibilityLabel={label}
|
||||||
|
accessibilityHint={undefined}
|
||||||
|
testID={`${testID}-datepicker`}
|
||||||
|
mode="date"
|
||||||
|
timeZoneName={'Etc/UTC'}
|
||||||
|
display="spinner"
|
||||||
|
// @ts-ignore applies in iOS only -prf
|
||||||
|
themeVariant={t.name === 'dark' ? 'dark' : 'light'}
|
||||||
|
value={new Date(value)}
|
||||||
|
onChange={onChangeInternal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import DateTimePicker, {
|
||||||
|
DateTimePickerEvent,
|
||||||
|
} from '@react-native-community/datetimepicker'
|
||||||
|
|
||||||
|
import {useTheme, atoms} from '#/alf'
|
||||||
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import {toSimpleDateString} from '#/components/forms/DateField/utils'
|
||||||
|
import {DateFieldProps} from '#/components/forms/DateField/types'
|
||||||
|
|
||||||
|
export * as utils from '#/components/forms/DateField/utils'
|
||||||
|
export const Label = TextField.Label
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date-only input. Accepts a date in the format YYYY-MM-DD, and reports date
|
||||||
|
* changes in the same format.
|
||||||
|
*
|
||||||
|
* For dates of unknown format, convert with the
|
||||||
|
* `utils.toSimpleDateString(Date)` export of this file.
|
||||||
|
*/
|
||||||
|
export function DateField({
|
||||||
|
value,
|
||||||
|
onChangeDate,
|
||||||
|
testID,
|
||||||
|
label,
|
||||||
|
}: DateFieldProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
const onChangeInternal = React.useCallback(
|
||||||
|
(event: DateTimePickerEvent, date: Date | undefined) => {
|
||||||
|
if (date) {
|
||||||
|
const formatted = toSimpleDateString(date)
|
||||||
|
onChangeDate(formatted)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChangeDate],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[atoms.relative, atoms.w_full]}>
|
||||||
|
<DateTimePicker
|
||||||
|
aria-label={label}
|
||||||
|
accessibilityLabel={label}
|
||||||
|
accessibilityHint={undefined}
|
||||||
|
testID={`${testID}-datepicker`}
|
||||||
|
mode="date"
|
||||||
|
timeZoneName={'Etc/UTC'}
|
||||||
|
display="spinner"
|
||||||
|
themeVariant={t.name === 'dark' ? 'dark' : 'light'}
|
||||||
|
value={new Date(value)}
|
||||||
|
onChange={onChangeInternal}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {TextInput, TextInputProps, StyleSheet} from 'react-native'
|
||||||
|
// @ts-ignore
|
||||||
|
import {unstable_createElement} from 'react-native-web'
|
||||||
|
|
||||||
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import {toSimpleDateString} from '#/components/forms/DateField/utils'
|
||||||
|
import {DateFieldProps} from '#/components/forms/DateField/types'
|
||||||
|
|
||||||
|
export * as utils from '#/components/forms/DateField/utils'
|
||||||
|
export const Label = TextField.Label
|
||||||
|
|
||||||
|
const InputBase = React.forwardRef<HTMLInputElement, TextInputProps>(
|
||||||
|
({style, ...props}, ref) => {
|
||||||
|
return unstable_createElement('input', {
|
||||||
|
...props,
|
||||||
|
ref,
|
||||||
|
type: 'date',
|
||||||
|
style: [
|
||||||
|
StyleSheet.flatten(style),
|
||||||
|
{
|
||||||
|
background: 'transparent',
|
||||||
|
border: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
InputBase.displayName = 'InputBase'
|
||||||
|
|
||||||
|
const Input = TextField.createInput(InputBase as unknown as typeof TextInput)
|
||||||
|
|
||||||
|
export function DateField({
|
||||||
|
value,
|
||||||
|
onChangeDate,
|
||||||
|
label,
|
||||||
|
isInvalid,
|
||||||
|
testID,
|
||||||
|
}: DateFieldProps) {
|
||||||
|
const handleOnChange = React.useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
const date = e.target.valueAsDate || e.target.value
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
const formatted = toSimpleDateString(date)
|
||||||
|
onChangeDate(formatted)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChangeDate],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextField.Root isInvalid={isInvalid}>
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
label={label}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
onChangeText={() => {}}
|
||||||
|
testID={testID}
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export type DateFieldProps = {
|
||||||
|
value: string
|
||||||
|
onChangeDate: (date: string) => void
|
||||||
|
label: string
|
||||||
|
isInvalid?: boolean
|
||||||
|
testID?: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import {getLocales} from 'expo-localization'
|
||||||
|
|
||||||
|
const LOCALE = getLocales()[0]
|
||||||
|
|
||||||
|
// we need the date in the form yyyy-MM-dd to pass to the input
|
||||||
|
export function toSimpleDateString(date: Date | string): string {
|
||||||
|
const _date = typeof date === 'string' ? new Date(date) : date
|
||||||
|
return _date.toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function localizeDate(date: Date | string): string {
|
||||||
|
const _date = typeof date === 'string' ? new Date(date) : date
|
||||||
|
return new Intl.DateTimeFormat(LOCALE.languageTag, {
|
||||||
|
timeZone: 'UTC',
|
||||||
|
}).format(_date)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
|
||||||
|
import {atoms, useTheme} from '#/alf'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT FINISHED, just here as a reference
|
||||||
|
*/
|
||||||
|
export function InputGroup(props: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const children = React.Children.toArray(props.children)
|
||||||
|
const total = children.length
|
||||||
|
return (
|
||||||
|
<View style={[atoms.w_full]}>
|
||||||
|
{children.map((child, i) => {
|
||||||
|
return React.isValidElement(child) ? (
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
{i > 0 ? (
|
||||||
|
<View
|
||||||
|
style={[atoms.border_b, {borderColor: t.palette.contrast_500}]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{React.cloneElement(child, {
|
||||||
|
// @ts-ignore
|
||||||
|
style: [
|
||||||
|
...(Array.isArray(child.props?.style)
|
||||||
|
? child.props.style
|
||||||
|
: [child.props.style || {}]),
|
||||||
|
{
|
||||||
|
borderTopLeftRadius: i > 0 ? 0 : undefined,
|
||||||
|
borderTopRightRadius: i > 0 ? 0 : undefined,
|
||||||
|
borderBottomLeftRadius: i < total - 1 ? 0 : undefined,
|
||||||
|
borderBottomRightRadius: i < total - 1 ? 0 : undefined,
|
||||||
|
borderBottomWidth: i < total - 1 ? 0 : undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
) : null
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
TextInput,
|
||||||
|
TextInputProps,
|
||||||
|
TextStyle,
|
||||||
|
ViewStyle,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
AccessibilityProps,
|
||||||
|
} from 'react-native'
|
||||||
|
|
||||||
|
import {HITSLOP_20} from 'lib/constants'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
|
import {useTheme, atoms as a, web, tokens, android} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
|
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||||
|
|
||||||
|
const Context = React.createContext<{
|
||||||
|
inputRef: React.RefObject<TextInput> | null
|
||||||
|
isInvalid: boolean
|
||||||
|
hovered: boolean
|
||||||
|
onHoverIn: () => void
|
||||||
|
onHoverOut: () => void
|
||||||
|
focused: boolean
|
||||||
|
onFocus: () => void
|
||||||
|
onBlur: () => void
|
||||||
|
}>({
|
||||||
|
inputRef: null,
|
||||||
|
isInvalid: false,
|
||||||
|
hovered: false,
|
||||||
|
onHoverIn: () => {},
|
||||||
|
onHoverOut: () => {},
|
||||||
|
focused: false,
|
||||||
|
onFocus: () => {},
|
||||||
|
onBlur: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type RootProps = React.PropsWithChildren<{isInvalid?: boolean}>
|
||||||
|
|
||||||
|
export function Root({children, isInvalid = false}: RootProps) {
|
||||||
|
const inputRef = React.useRef<TextInput>(null)
|
||||||
|
const rootRef = React.useRef<View>(null)
|
||||||
|
const {
|
||||||
|
state: hovered,
|
||||||
|
onIn: onHoverIn,
|
||||||
|
onOut: onHoverOut,
|
||||||
|
} = useInteractionState()
|
||||||
|
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||||
|
|
||||||
|
const context = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
inputRef,
|
||||||
|
hovered,
|
||||||
|
onHoverIn,
|
||||||
|
onHoverOut,
|
||||||
|
focused,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
|
isInvalid,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
inputRef,
|
||||||
|
hovered,
|
||||||
|
onHoverIn,
|
||||||
|
onHoverOut,
|
||||||
|
focused,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
|
isInvalid,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
const root = rootRef.current
|
||||||
|
if (!root || !isWeb) return
|
||||||
|
// @ts-ignore web only
|
||||||
|
root.tabIndex = -1
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={context}>
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="button"
|
||||||
|
ref={rootRef}
|
||||||
|
role="none"
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.relative,
|
||||||
|
a.w_full,
|
||||||
|
a.px_md,
|
||||||
|
{
|
||||||
|
paddingVertical: 14,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
// onPressIn/out don't work on android web
|
||||||
|
onPress={() => inputRef.current?.focus()}
|
||||||
|
onHoverIn={onHoverIn}
|
||||||
|
onHoverOut={onHoverOut}>
|
||||||
|
{children}
|
||||||
|
</Pressable>
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSharedInputStyles() {
|
||||||
|
const t = useTheme()
|
||||||
|
return React.useMemo(() => {
|
||||||
|
const hover: ViewStyle[] = [
|
||||||
|
{
|
||||||
|
borderColor: t.palette.contrast_100,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const focus: ViewStyle[] = [
|
||||||
|
{
|
||||||
|
backgroundColor: t.palette.contrast_50,
|
||||||
|
borderColor: t.palette.primary_500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const error: ViewStyle[] = [
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
|
||||||
|
borderColor:
|
||||||
|
t.name === 'light' ? t.palette.negative_300 : t.palette.negative_800,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const errorHover: ViewStyle[] = [
|
||||||
|
{
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
|
||||||
|
borderColor: tokens.color.red_500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
chromeHover: StyleSheet.flatten(hover),
|
||||||
|
chromeFocus: StyleSheet.flatten(focus),
|
||||||
|
chromeError: StyleSheet.flatten(error),
|
||||||
|
chromeErrorHover: StyleSheet.flatten(errorHover),
|
||||||
|
}
|
||||||
|
}, [t])
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
onChangeText: (value: string) => void
|
||||||
|
isInvalid?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createInput(Component: typeof TextInput) {
|
||||||
|
return function Input({
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
value,
|
||||||
|
onChangeText,
|
||||||
|
isInvalid,
|
||||||
|
...rest
|
||||||
|
}: InputProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
const ctx = React.useContext(Context)
|
||||||
|
const withinRoot = Boolean(ctx.inputRef)
|
||||||
|
|
||||||
|
const {chromeHover, chromeFocus, chromeError, chromeErrorHover} =
|
||||||
|
useSharedInputStyles()
|
||||||
|
|
||||||
|
if (!withinRoot) {
|
||||||
|
return (
|
||||||
|
<Root isInvalid={isInvalid}>
|
||||||
|
<Input
|
||||||
|
label={label}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={value}
|
||||||
|
onChangeText={onChangeText}
|
||||||
|
isInvalid={isInvalid}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Component
|
||||||
|
accessibilityHint={undefined}
|
||||||
|
{...rest}
|
||||||
|
aria-label={label}
|
||||||
|
accessibilityLabel={label}
|
||||||
|
ref={ctx.inputRef}
|
||||||
|
value={value}
|
||||||
|
onChangeText={onChangeText}
|
||||||
|
onFocus={ctx.onFocus}
|
||||||
|
onBlur={ctx.onBlur}
|
||||||
|
placeholder={placeholder || label}
|
||||||
|
placeholderTextColor={t.palette.contrast_500}
|
||||||
|
hitSlop={HITSLOP_20}
|
||||||
|
style={[
|
||||||
|
a.relative,
|
||||||
|
a.z_20,
|
||||||
|
a.flex_1,
|
||||||
|
a.text_md,
|
||||||
|
t.atoms.text,
|
||||||
|
a.px_xs,
|
||||||
|
android({
|
||||||
|
paddingBottom: 2,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
lineHeight: a.text_md.lineHeight * 1.1875,
|
||||||
|
textAlignVertical: rest.multiline ? 'top' : undefined,
|
||||||
|
minHeight: rest.multiline ? 60 : undefined,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.z_10,
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
a.rounded_sm,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{borderColor: 'transparent', borderWidth: 2},
|
||||||
|
ctx.hovered ? chromeHover : {},
|
||||||
|
ctx.focused ? chromeFocus : {},
|
||||||
|
ctx.isInvalid || isInvalid ? chromeError : {},
|
||||||
|
(ctx.isInvalid || isInvalid) && (ctx.hovered || ctx.focused)
|
||||||
|
? chromeErrorHover
|
||||||
|
: {},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Input = createInput(TextInput)
|
||||||
|
|
||||||
|
export function Label({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_600, a.mb_sm]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Icon({icon: Comp}: {icon: React.ComponentType<SVGIconProps>}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const ctx = React.useContext(Context)
|
||||||
|
const {hover, focus, errorHover, errorFocus} = React.useMemo(() => {
|
||||||
|
const hover: TextStyle[] = [
|
||||||
|
{
|
||||||
|
color: t.palette.contrast_800,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const focus: TextStyle[] = [
|
||||||
|
{
|
||||||
|
color: t.palette.primary_500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const errorHover: TextStyle[] = [
|
||||||
|
{
|
||||||
|
color: t.palette.negative_500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const errorFocus: TextStyle[] = [
|
||||||
|
{
|
||||||
|
color: t.palette.negative_500,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
hover,
|
||||||
|
focus,
|
||||||
|
errorHover,
|
||||||
|
errorFocus,
|
||||||
|
}
|
||||||
|
}, [t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.z_20, a.pr_xs]}>
|
||||||
|
<Comp
|
||||||
|
size="md"
|
||||||
|
style={[
|
||||||
|
{color: t.palette.contrast_500, pointerEvents: 'none'},
|
||||||
|
ctx.hovered ? hover : {},
|
||||||
|
ctx.focused ? focus : {},
|
||||||
|
ctx.isInvalid && ctx.hovered ? errorHover : {},
|
||||||
|
ctx.isInvalid && ctx.focused ? errorFocus : {},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Suffix({
|
||||||
|
children,
|
||||||
|
label,
|
||||||
|
accessibilityHint,
|
||||||
|
}: React.PropsWithChildren<{
|
||||||
|
label: string
|
||||||
|
accessibilityHint?: AccessibilityProps['accessibilityHint']
|
||||||
|
}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const ctx = React.useContext(Context)
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
aria-label={label}
|
||||||
|
accessibilityLabel={label}
|
||||||
|
accessibilityHint={accessibilityHint}
|
||||||
|
style={[
|
||||||
|
a.z_20,
|
||||||
|
a.pr_sm,
|
||||||
|
a.text_md,
|
||||||
|
t.atoms.text_contrast_400,
|
||||||
|
{
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
web({
|
||||||
|
marginTop: -2,
|
||||||
|
}),
|
||||||
|
ctx.hovered || ctx.focused
|
||||||
|
? {
|
||||||
|
color: t.palette.contrast_800,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {Pressable, View, ViewStyle} from 'react-native'
|
||||||
|
|
||||||
|
import {HITSLOP_10} from 'lib/constants'
|
||||||
|
import {useTheme, atoms as a, web, native} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
|
|
||||||
|
export type ItemState = {
|
||||||
|
name: string
|
||||||
|
selected: boolean
|
||||||
|
disabled: boolean
|
||||||
|
isInvalid: boolean
|
||||||
|
hovered: boolean
|
||||||
|
pressed: boolean
|
||||||
|
focused: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemContext = React.createContext<ItemState>({
|
||||||
|
name: '',
|
||||||
|
selected: false,
|
||||||
|
disabled: false,
|
||||||
|
isInvalid: false,
|
||||||
|
hovered: false,
|
||||||
|
pressed: false,
|
||||||
|
focused: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const GroupContext = React.createContext<{
|
||||||
|
values: string[]
|
||||||
|
disabled: boolean
|
||||||
|
type: 'radio' | 'checkbox'
|
||||||
|
maxSelectionsReached: boolean
|
||||||
|
setFieldValue: (props: {name: string; value: boolean}) => void
|
||||||
|
}>({
|
||||||
|
type: 'checkbox',
|
||||||
|
values: [],
|
||||||
|
disabled: false,
|
||||||
|
maxSelectionsReached: false,
|
||||||
|
setFieldValue: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type GroupProps = React.PropsWithChildren<{
|
||||||
|
type?: 'radio' | 'checkbox'
|
||||||
|
values: string[]
|
||||||
|
maxSelections?: number
|
||||||
|
disabled?: boolean
|
||||||
|
onChange: (value: string[]) => void
|
||||||
|
label: string
|
||||||
|
}>
|
||||||
|
|
||||||
|
export type ItemProps = {
|
||||||
|
type?: 'radio' | 'checkbox'
|
||||||
|
name: string
|
||||||
|
label: string
|
||||||
|
value?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
onChange?: (selected: boolean) => void
|
||||||
|
isInvalid?: boolean
|
||||||
|
style?: (state: ItemState) => ViewStyle
|
||||||
|
children: ((props: ItemState) => React.ReactNode) | React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useItemContext() {
|
||||||
|
return React.useContext(ItemContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Group({
|
||||||
|
children,
|
||||||
|
values: providedValues,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
type = 'checkbox',
|
||||||
|
maxSelections,
|
||||||
|
label,
|
||||||
|
}: GroupProps) {
|
||||||
|
const groupRole = type === 'radio' ? 'radiogroup' : undefined
|
||||||
|
const values = type === 'radio' ? providedValues.slice(0, 1) : providedValues
|
||||||
|
const [maxReached, setMaxReached] = React.useState(false)
|
||||||
|
|
||||||
|
const setFieldValue = React.useCallback<
|
||||||
|
(props: {name: string; value: boolean}) => void
|
||||||
|
>(
|
||||||
|
({name, value}) => {
|
||||||
|
if (type === 'checkbox') {
|
||||||
|
const pruned = values.filter(v => v !== name)
|
||||||
|
const next = value ? pruned.concat(name) : pruned
|
||||||
|
onChange(next)
|
||||||
|
} else {
|
||||||
|
onChange([name])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[type, onChange, values],
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (type === 'checkbox') {
|
||||||
|
if (
|
||||||
|
maxSelections &&
|
||||||
|
values.length >= maxSelections &&
|
||||||
|
maxReached === false
|
||||||
|
) {
|
||||||
|
setMaxReached(true)
|
||||||
|
} else if (
|
||||||
|
maxSelections &&
|
||||||
|
values.length < maxSelections &&
|
||||||
|
maxReached === true
|
||||||
|
) {
|
||||||
|
setMaxReached(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [type, values.length, maxSelections, maxReached, setMaxReached])
|
||||||
|
|
||||||
|
const context = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
values,
|
||||||
|
type,
|
||||||
|
disabled,
|
||||||
|
maxSelectionsReached: maxReached,
|
||||||
|
setFieldValue,
|
||||||
|
}),
|
||||||
|
[values, disabled, type, maxReached, setFieldValue],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GroupContext.Provider value={context}>
|
||||||
|
<View
|
||||||
|
role={groupRole}
|
||||||
|
{...(groupRole === 'radiogroup'
|
||||||
|
? {
|
||||||
|
'aria-label': label,
|
||||||
|
accessibilityLabel: label,
|
||||||
|
accessibilityRole: groupRole,
|
||||||
|
}
|
||||||
|
: {})}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</GroupContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Item({
|
||||||
|
children,
|
||||||
|
name,
|
||||||
|
value = false,
|
||||||
|
disabled: itemDisabled = false,
|
||||||
|
onChange,
|
||||||
|
isInvalid,
|
||||||
|
style,
|
||||||
|
type = 'checkbox',
|
||||||
|
label,
|
||||||
|
...rest
|
||||||
|
}: ItemProps) {
|
||||||
|
const {
|
||||||
|
values: selectedValues,
|
||||||
|
type: groupType,
|
||||||
|
disabled: groupDisabled,
|
||||||
|
setFieldValue,
|
||||||
|
maxSelectionsReached,
|
||||||
|
} = React.useContext(GroupContext)
|
||||||
|
const {
|
||||||
|
state: hovered,
|
||||||
|
onIn: onHoverIn,
|
||||||
|
onOut: onHoverOut,
|
||||||
|
} = useInteractionState()
|
||||||
|
const {
|
||||||
|
state: pressed,
|
||||||
|
onIn: onPressIn,
|
||||||
|
onOut: onPressOut,
|
||||||
|
} = useInteractionState()
|
||||||
|
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||||
|
|
||||||
|
const role = groupType === 'radio' ? 'radio' : type
|
||||||
|
const selected = selectedValues.includes(name) || !!value
|
||||||
|
const disabled =
|
||||||
|
groupDisabled || itemDisabled || (!selected && maxSelectionsReached)
|
||||||
|
|
||||||
|
const onPress = React.useCallback(() => {
|
||||||
|
const next = !selected
|
||||||
|
setFieldValue({name, value: next})
|
||||||
|
onChange?.(next)
|
||||||
|
}, [name, selected, onChange, setFieldValue])
|
||||||
|
|
||||||
|
const state = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
name,
|
||||||
|
selected,
|
||||||
|
disabled: disabled ?? false,
|
||||||
|
isInvalid: isInvalid ?? false,
|
||||||
|
hovered,
|
||||||
|
pressed,
|
||||||
|
focused,
|
||||||
|
}),
|
||||||
|
[name, selected, disabled, hovered, pressed, focused, isInvalid],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemContext.Provider value={state}>
|
||||||
|
<Pressable
|
||||||
|
accessibilityHint={undefined} // optional
|
||||||
|
hitSlop={HITSLOP_10}
|
||||||
|
{...rest}
|
||||||
|
disabled={disabled}
|
||||||
|
aria-disabled={disabled ?? false}
|
||||||
|
aria-checked={selected}
|
||||||
|
aria-invalid={isInvalid}
|
||||||
|
aria-label={label}
|
||||||
|
role={role}
|
||||||
|
accessibilityRole={role}
|
||||||
|
accessibilityState={{
|
||||||
|
disabled: disabled ?? false,
|
||||||
|
selected: selected,
|
||||||
|
}}
|
||||||
|
accessibilityLabel={label}
|
||||||
|
onPress={onPress}
|
||||||
|
onHoverIn={onHoverIn}
|
||||||
|
onHoverOut={onHoverOut}
|
||||||
|
onPressIn={onPressIn}
|
||||||
|
onPressOut={onPressOut}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
focused ? web({outline: 'none'}) : {},
|
||||||
|
style?.(state),
|
||||||
|
]}>
|
||||||
|
{typeof children === 'function' ? children(state) : children}
|
||||||
|
</Pressable>
|
||||||
|
</ItemContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Label({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {disabled} = useItemContext()
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.font_bold,
|
||||||
|
{
|
||||||
|
userSelect: 'none',
|
||||||
|
color: disabled ? t.palette.contrast_400 : t.palette.contrast_600,
|
||||||
|
},
|
||||||
|
native({
|
||||||
|
paddingTop: 3,
|
||||||
|
}),
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(eric) refactor to memoize styles without knowledge of state
|
||||||
|
export function createSharedToggleStyles({
|
||||||
|
theme: t,
|
||||||
|
hovered,
|
||||||
|
focused,
|
||||||
|
selected,
|
||||||
|
disabled,
|
||||||
|
isInvalid,
|
||||||
|
}: {
|
||||||
|
theme: ReturnType<typeof useTheme>
|
||||||
|
selected: boolean
|
||||||
|
hovered: boolean
|
||||||
|
focused: boolean
|
||||||
|
disabled: boolean
|
||||||
|
isInvalid: boolean
|
||||||
|
}) {
|
||||||
|
const base: ViewStyle[] = []
|
||||||
|
const baseHover: ViewStyle[] = []
|
||||||
|
const indicator: ViewStyle[] = []
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
base.push({
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.primary_25 : t.palette.primary_900,
|
||||||
|
borderColor: t.palette.primary_500,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hovered || focused) {
|
||||||
|
baseHover.push({
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.primary_100 : t.palette.primary_800,
|
||||||
|
borderColor:
|
||||||
|
t.name === 'light' ? t.palette.primary_600 : t.palette.primary_400,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hovered || focused) {
|
||||||
|
baseHover.push({
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.contrast_50 : t.palette.contrast_100,
|
||||||
|
borderColor: t.palette.contrast_500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInvalid) {
|
||||||
|
base.push({
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
|
||||||
|
borderColor:
|
||||||
|
t.name === 'light' ? t.palette.negative_300 : t.palette.negative_800,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hovered || focused) {
|
||||||
|
baseHover.push({
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
|
||||||
|
borderColor: t.palette.negative_500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
base.push({
|
||||||
|
backgroundColor: t.palette.contrast_100,
|
||||||
|
borderColor: t.palette.contrast_400,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseStyles: base,
|
||||||
|
baseHoverStyles: disabled ? [] : baseHover,
|
||||||
|
indicatorStyles: indicator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Checkbox() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {selected, hovered, focused, disabled, isInvalid} = useItemContext()
|
||||||
|
const {baseStyles, baseHoverStyles, indicatorStyles} =
|
||||||
|
createSharedToggleStyles({
|
||||||
|
theme: t,
|
||||||
|
hovered,
|
||||||
|
focused,
|
||||||
|
selected,
|
||||||
|
disabled,
|
||||||
|
isInvalid,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.justify_center,
|
||||||
|
a.align_center,
|
||||||
|
a.border,
|
||||||
|
a.rounded_xs,
|
||||||
|
t.atoms.border_contrast,
|
||||||
|
{
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
baseStyles,
|
||||||
|
hovered || focused ? baseHoverStyles : {},
|
||||||
|
]}>
|
||||||
|
{selected ? (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.rounded_2xs,
|
||||||
|
{height: 12, width: 12},
|
||||||
|
selected
|
||||||
|
? {
|
||||||
|
backgroundColor: t.palette.primary_500,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
indicatorStyles,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Switch() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {selected, hovered, focused, disabled, isInvalid} = useItemContext()
|
||||||
|
const {baseStyles, baseHoverStyles, indicatorStyles} =
|
||||||
|
createSharedToggleStyles({
|
||||||
|
theme: t,
|
||||||
|
hovered,
|
||||||
|
focused,
|
||||||
|
selected,
|
||||||
|
disabled,
|
||||||
|
isInvalid,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.relative,
|
||||||
|
a.border,
|
||||||
|
a.rounded_full,
|
||||||
|
t.atoms.bg,
|
||||||
|
t.atoms.border_contrast,
|
||||||
|
{
|
||||||
|
height: 20,
|
||||||
|
width: 30,
|
||||||
|
},
|
||||||
|
baseStyles,
|
||||||
|
hovered || focused ? baseHoverStyles : {},
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.rounded_full,
|
||||||
|
{
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
top: 3,
|
||||||
|
left: 3,
|
||||||
|
backgroundColor: t.palette.contrast_400,
|
||||||
|
},
|
||||||
|
selected
|
||||||
|
? {
|
||||||
|
backgroundColor: t.palette.primary_500,
|
||||||
|
left: 13,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
indicatorStyles,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Radio() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {selected, hovered, focused, disabled, isInvalid} =
|
||||||
|
React.useContext(ItemContext)
|
||||||
|
const {baseStyles, baseHoverStyles, indicatorStyles} =
|
||||||
|
createSharedToggleStyles({
|
||||||
|
theme: t,
|
||||||
|
hovered,
|
||||||
|
focused,
|
||||||
|
selected,
|
||||||
|
disabled,
|
||||||
|
isInvalid,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.justify_center,
|
||||||
|
a.align_center,
|
||||||
|
a.border,
|
||||||
|
a.rounded_full,
|
||||||
|
t.atoms.border_contrast,
|
||||||
|
{
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
baseStyles,
|
||||||
|
hovered || focused ? baseHoverStyles : {},
|
||||||
|
]}>
|
||||||
|
{selected ? (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.rounded_full,
|
||||||
|
{height: 12, width: 12},
|
||||||
|
selected
|
||||||
|
? {
|
||||||
|
backgroundColor: t.palette.primary_500,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
indicatorStyles,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View, AccessibilityProps, TextStyle, ViewStyle} from 'react-native'
|
||||||
|
|
||||||
|
import {atoms as a, useTheme, native} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
|
||||||
|
export type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> &
|
||||||
|
AccessibilityProps &
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
|
||||||
|
export type GroupProps = Omit<Toggle.GroupProps, 'style' | 'type'> & {
|
||||||
|
multiple?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Group({children, multiple, ...props}: GroupProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<Toggle.Group type={multiple ? 'checkbox' : 'radio'} {...props}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.border,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.overflow_hidden,
|
||||||
|
t.atoms.border,
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</Toggle.Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button({children, ...props}: ItemProps) {
|
||||||
|
return (
|
||||||
|
<Toggle.Item {...props}>
|
||||||
|
<ButtonInner>{children}</ButtonInner>
|
||||||
|
</Toggle.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
const state = Toggle.useItemContext()
|
||||||
|
|
||||||
|
const {baseStyles, hoverStyles, activeStyles, textStyles} =
|
||||||
|
React.useMemo(() => {
|
||||||
|
const base: ViewStyle[] = []
|
||||||
|
const hover: ViewStyle[] = []
|
||||||
|
const active: ViewStyle[] = []
|
||||||
|
const text: TextStyle[] = []
|
||||||
|
|
||||||
|
hover.push(
|
||||||
|
t.name === 'light' ? t.atoms.bg_contrast_100 : t.atoms.bg_contrast_25,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (state.selected) {
|
||||||
|
active.push({
|
||||||
|
backgroundColor: t.palette.contrast_800,
|
||||||
|
})
|
||||||
|
text.push(t.atoms.text_inverted)
|
||||||
|
hover.push({
|
||||||
|
backgroundColor: t.palette.contrast_800,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (state.disabled) {
|
||||||
|
active.push({
|
||||||
|
backgroundColor: t.palette.contrast_500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.disabled) {
|
||||||
|
base.push({
|
||||||
|
backgroundColor: t.palette.contrast_100,
|
||||||
|
})
|
||||||
|
text.push({
|
||||||
|
opacity: 0.5,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseStyles: base,
|
||||||
|
hoverStyles: hover,
|
||||||
|
activeStyles: active,
|
||||||
|
textStyles: text,
|
||||||
|
}
|
||||||
|
}, [t, state])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
borderLeftWidth: 1,
|
||||||
|
marginLeft: -1,
|
||||||
|
},
|
||||||
|
a.px_lg,
|
||||||
|
a.py_md,
|
||||||
|
native({
|
||||||
|
paddingTop: 14,
|
||||||
|
}),
|
||||||
|
t.atoms.bg,
|
||||||
|
t.atoms.border,
|
||||||
|
baseStyles,
|
||||||
|
activeStyles,
|
||||||
|
(state.hovered || state.focused || state.pressed) && hoverStyles,
|
||||||
|
]}>
|
||||||
|
{typeof children === 'string' ? (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_center,
|
||||||
|
a.font_bold,
|
||||||
|
t.atoms.text_contrast_500,
|
||||||
|
textStyles,
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function useInteractionState() {
|
||||||
|
const [state, setState] = React.useState(false)
|
||||||
|
|
||||||
|
const onIn = React.useCallback(() => {
|
||||||
|
setState(true)
|
||||||
|
}, [setState])
|
||||||
|
const onOut = React.useCallback(() => {
|
||||||
|
setState(false)
|
||||||
|
}, [setState])
|
||||||
|
|
||||||
|
return React.useMemo(
|
||||||
|
() => ({
|
||||||
|
state,
|
||||||
|
onIn,
|
||||||
|
onOut,
|
||||||
|
}),
|
||||||
|
[state, onIn, onOut],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const ArrowTopRight_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M8 6a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v9a1 1 0 1 1-2 0V8.414l-9.793 9.793a1 1 0 0 1-1.414-1.414L15.586 7H9a1 1 0 0 1-1-1Z',
|
||||||
|
})
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const CalendarDays_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M4 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Zm1 16V9h14v10H5ZM5 7h14V5H5v2Zm3 10.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM17.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM12 13.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM9.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM12 17.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z',
|
||||||
|
})
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const ColorPalette_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M4 12c0-4.09 3.527-7.5 8-7.5s8 3.41 8 7.5c0 1.579-.419 2.056-.708 2.236-.388.241-1.031.286-2.058.153-.33-.043-.652-.096-.991-.152a65.905 65.905 0 0 0-.531-.087c-.52-.081-1.077-.156-1.61-.164-1.065-.016-2.336.245-2.996 1.567-.418.834-.295 1.67-.078 2.314.18.534.47 1.055.683 1.437v.001l.097.175.01.018C7.432 19.407 4 16.033 4 12Zm8-9.5C6.532 2.5 2 6.7 2 12s4.532 9.5 10 9.5c.401 0 .812-.04 1.166-.193.41-.176.761-.517.866-1.028.085-.416-.03-.796-.118-1.029a5.981 5.981 0 0 0-.351-.73l-.12-.215c-.215-.392-.403-.73-.52-1.078-.13-.387-.111-.614-.029-.78.146-.291.404-.473 1.178-.461.385.005.825.06 1.329.14.15.023.308.05.47.077.36.059.742.122 1.105.17 1.021.132 2.325.213 3.373-.439C21.496 15.22 22 13.874 22 12c0-5.3-4.532-9.5-10-9.5Zm3.5 8.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM9 12.25a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm1.5-2.75a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z',
|
||||||
|
})
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const Globe_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M4.062 11h2.961c.103-2.204.545-4.218 1.235-5.77.06-.136.123-.269.188-.399A8.007 8.007 0 0 0 4.062 11ZM12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm0 2c-.227 0-.518.1-.868.432-.354.337-.719.872-1.047 1.61-.561 1.263-.958 2.991-1.06 4.958h5.95c-.102-1.967-.499-3.695-1.06-4.958-.328-.738-.693-1.273-1.047-1.61C12.518 4.099 12.227 4 12 4Zm4.977 7c-.103-2.204-.545-4.218-1.235-5.77a9.78 9.78 0 0 0-.188-.399A8.006 8.006 0 0 1 19.938 11h-2.961Zm-2.003 2H9.026c.101 1.966.498 3.695 1.06 4.958.327.738.692 1.273 1.046 1.61.35.333.641.432.868.432.227 0 .518-.1.868-.432.354-.337.719-.872 1.047-1.61.561-1.263.958-2.991 1.06-4.958Zm.58 6.169c.065-.13.128-.263.188-.399.69-1.552 1.132-3.566 1.235-5.77h2.961a8.006 8.006 0 0 1-4.384 6.169Zm-7.108 0a9.877 9.877 0 0 1-.188-.399c-.69-1.552-1.132-3.566-1.235-5.77H4.062a8.006 8.006 0 0 0 4.384 6.169Z',
|
||||||
|
})
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Svg, {Path} from 'react-native-svg'
|
||||||
|
|
||||||
|
import {useCommonSVGProps, Props} from '#/components/icons/common'
|
||||||
|
|
||||||
|
export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef(
|
||||||
|
function LogoImpl(props: Props, ref) {
|
||||||
|
const {fill, size, style, ...rest} = useCommonSVGProps(props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Svg
|
||||||
|
fill="none"
|
||||||
|
{...rest}
|
||||||
|
// @ts-ignore it's fiiiiine
|
||||||
|
ref={ref}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
style={[style]}>
|
||||||
|
<Path
|
||||||
|
fill={fill}
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M4.062 11h2.961c.103-2.204.545-4.218 1.235-5.77.06-.136.123-.269.188-.399A8.007 8.007 0 0 0 4.062 11ZM12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm0 2c-.227 0-.518.1-.868.432-.354.337-.719.872-1.047 1.61-.561 1.263-.958 2.991-1.06 4.958h5.95c-.102-1.967-.499-3.695-1.06-4.958-.328-.738-.693-1.273-1.047-1.61C12.518 4.099 12.227 4 12 4Zm4.977 7c-.103-2.204-.545-4.218-1.235-5.77a9.78 9.78 0 0 0-.188-.399A8.006 8.006 0 0 1 19.938 11h-2.961Zm-2.003 2H9.026c.101 1.966.498 3.695 1.06 4.958.327.738.692 1.273 1.046 1.61.35.333.641.432.868.432.227 0 .518-.1.868-.432.354-.337.719-.872 1.047-1.61.561-1.263.958-2.991 1.06-4.958Zm.58 6.169c.065-.13.128-.263.188-.399.69-1.552 1.132-3.566 1.235-5.77h2.961a8.006 8.006 0 0 1-4.384 6.169Zm-7.108 0a9.877 9.877 0 0 1-.188-.399c-.69-1.552-1.132-3.566-1.235-5.77H4.062a8.006 8.006 0 0 0 4.384 6.169Z"
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export function createSinglePathSVG({path}: {path: string}) {
|
||||||
|
return React.forwardRef<Svg, Props>(function LogoImpl(props, ref) {
|
||||||
|
const {fill, size, style, ...rest} = useCommonSVGProps(props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Svg
|
||||||
|
fill="none"
|
||||||
|
{...rest}
|
||||||
|
ref={ref}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
style={[style]}>
|
||||||
|
<Path fill={fill} fillRule="evenodd" clipRule="evenodd" d={path} />
|
||||||
|
</Svg>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import {StyleSheet, TextProps} from 'react-native'
|
||||||
|
import type {SvgProps, PathProps} from 'react-native-svg'
|
||||||
|
|
||||||
|
import {tokens} from '#/alf'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
fill?: PathProps['fill']
|
||||||
|
style?: TextProps['style']
|
||||||
|
size?: keyof typeof sizes
|
||||||
|
} & Omit<SvgProps, 'style' | 'size'>
|
||||||
|
|
||||||
|
export const sizes = {
|
||||||
|
xs: 12,
|
||||||
|
sm: 16,
|
||||||
|
md: 20,
|
||||||
|
lg: 24,
|
||||||
|
xl: 28,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCommonSVGProps(props: Props) {
|
||||||
|
const {fill, size, ...rest} = props
|
||||||
|
const style = StyleSheet.flatten(rest.style)
|
||||||
|
const _fill = fill || style?.color || tokens.color.blue_500
|
||||||
|
const _size = Number(size ? sizes[size] : rest.width || sizes.md)
|
||||||
|
|
||||||
|
return {
|
||||||
|
fill: _fill,
|
||||||
|
size: _size,
|
||||||
|
style,
|
||||||
|
...rest,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,20 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {FeedAPI, FeedAPIResponse} from './types'
|
import {FeedAPI, FeedAPIResponse} from './types'
|
||||||
import {getAgent} from '#/state/session'
|
import {getAgent} from '#/state/session'
|
||||||
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
|
|
||||||
export class CustomFeedAPI implements FeedAPI {
|
export class CustomFeedAPI implements FeedAPI {
|
||||||
constructor(public params: GetCustomFeed.QueryParams) {}
|
constructor(public params: GetCustomFeed.QueryParams) {}
|
||||||
|
|
||||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||||
const res = await getAgent().app.bsky.feed.getFeed({
|
const contentLangs = getContentLanguages().join(',')
|
||||||
...this.params,
|
const res = await getAgent().app.bsky.feed.getFeed(
|
||||||
limit: 1,
|
{
|
||||||
})
|
...this.params,
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
{headers: {'Accept-Language': contentLangs}},
|
||||||
|
)
|
||||||
return res.data.feed[0]
|
return res.data.feed[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,11 +28,15 @@ export class CustomFeedAPI implements FeedAPI {
|
|||||||
cursor: string | undefined
|
cursor: string | undefined
|
||||||
limit: number
|
limit: number
|
||||||
}): Promise<FeedAPIResponse> {
|
}): Promise<FeedAPIResponse> {
|
||||||
const res = await getAgent().app.bsky.feed.getFeed({
|
const contentLangs = getContentLanguages().join(',')
|
||||||
...this.params,
|
const res = await getAgent().app.bsky.feed.getFeed(
|
||||||
cursor,
|
{
|
||||||
limit,
|
...this.params,
|
||||||
})
|
cursor,
|
||||||
|
limit,
|
||||||
|
},
|
||||||
|
{headers: {'Accept-Language': contentLangs}},
|
||||||
|
)
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
// NOTE
|
// NOTE
|
||||||
// some custom feeds fail to enforce the pagination limit
|
// some custom feeds fail to enforce the pagination limit
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import {AppBskyFeedDefs} from '@atproto/api'
|
||||||
|
import {FeedAPI, FeedAPIResponse} from './types'
|
||||||
|
import {FollowingFeedAPI} from './following'
|
||||||
|
import {CustomFeedAPI} from './custom'
|
||||||
|
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
||||||
|
|
||||||
|
// HACK
|
||||||
|
// the feed API does not include any facilities for passing down
|
||||||
|
// non-post elements. adding that is a bit of a heavy lift, and we
|
||||||
|
// have just one temporary usecase for it: flagging when the home feed
|
||||||
|
// falls back to discover.
|
||||||
|
// we use this fallback marker post to drive this instead. see Feed.tsx
|
||||||
|
// for the usage.
|
||||||
|
// -prf
|
||||||
|
export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = {
|
||||||
|
post: {
|
||||||
|
uri: 'fallback-marker-post',
|
||||||
|
cid: 'fake',
|
||||||
|
record: {},
|
||||||
|
author: {
|
||||||
|
did: 'did:fake',
|
||||||
|
handle: 'fake.com',
|
||||||
|
},
|
||||||
|
indexedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HomeFeedAPI implements FeedAPI {
|
||||||
|
following: FollowingFeedAPI
|
||||||
|
discover: CustomFeedAPI
|
||||||
|
usingDiscover = false
|
||||||
|
itemCursor = 0
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.following = new FollowingFeedAPI()
|
||||||
|
this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')})
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.following = new FollowingFeedAPI()
|
||||||
|
this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')})
|
||||||
|
this.usingDiscover = false
|
||||||
|
this.itemCursor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||||
|
if (this.usingDiscover) {
|
||||||
|
return this.discover.peekLatest()
|
||||||
|
}
|
||||||
|
return this.following.peekLatest()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch({
|
||||||
|
cursor,
|
||||||
|
limit,
|
||||||
|
}: {
|
||||||
|
cursor: string | undefined
|
||||||
|
limit: number
|
||||||
|
}): Promise<FeedAPIResponse> {
|
||||||
|
if (!cursor) {
|
||||||
|
this.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnCursor
|
||||||
|
let posts: AppBskyFeedDefs.FeedViewPost[] = []
|
||||||
|
|
||||||
|
if (!this.usingDiscover) {
|
||||||
|
const res = await this.following.fetch({cursor, limit})
|
||||||
|
returnCursor = res.cursor
|
||||||
|
posts = posts.concat(res.feed)
|
||||||
|
if (!returnCursor) {
|
||||||
|
cursor = ''
|
||||||
|
posts.push(FALLBACK_MARKER_POST)
|
||||||
|
this.usingDiscover = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.usingDiscover) {
|
||||||
|
const res = await this.discover.fetch({cursor, limit})
|
||||||
|
returnCursor = res.cursor
|
||||||
|
posts = posts.concat(res.feed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cursor: returnCursor,
|
||||||
|
feed: posts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
|
|||||||
import {FeedParams} from '#/state/queries/post-feed'
|
import {FeedParams} from '#/state/queries/post-feed'
|
||||||
import {FeedTunerFn} from '../feed-manip'
|
import {FeedTunerFn} from '../feed-manip'
|
||||||
import {getAgent} from '#/state/session'
|
import {getAgent} from '#/state/session'
|
||||||
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
|
|
||||||
const REQUEST_WAIT_MS = 500 // 500ms
|
const REQUEST_WAIT_MS = 500 // 500ms
|
||||||
const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
|
const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
|
||||||
@@ -25,7 +26,7 @@ export class MergeFeedAPI implements FeedAPI {
|
|||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.following = new MergeFeedSource_Following(this.feedTuners)
|
this.following = new MergeFeedSource_Following(this.feedTuners)
|
||||||
this.customFeeds = [] // just empty the array, they will be captured in _fetchNext()
|
this.customFeeds = []
|
||||||
this.feedCursor = 0
|
this.feedCursor = 0
|
||||||
this.itemCursor = 0
|
this.itemCursor = 0
|
||||||
this.sampleCursor = 0
|
this.sampleCursor = 0
|
||||||
@@ -231,11 +232,15 @@ class MergeFeedSource_Custom extends MergeFeedSource {
|
|||||||
limit: number,
|
limit: number,
|
||||||
): Promise<AppBskyFeedGetTimeline.Response> {
|
): Promise<AppBskyFeedGetTimeline.Response> {
|
||||||
try {
|
try {
|
||||||
const res = await getAgent().app.bsky.feed.getFeed({
|
const contentLangs = getContentLanguages().join(',')
|
||||||
cursor,
|
const res = await getAgent().app.bsky.feed.getFeed(
|
||||||
limit,
|
{
|
||||||
feed: this.feedUri,
|
cursor,
|
||||||
})
|
limit,
|
||||||
|
feed: this.feedUri,
|
||||||
|
},
|
||||||
|
{headers: {'Accept-Language': contentLangs}},
|
||||||
|
)
|
||||||
// NOTE
|
// NOTE
|
||||||
// some custom feeds fail to enforce the pagination limit
|
// some custom feeds fail to enforce the pagination limit
|
||||||
// so we manually truncate here
|
// so we manually truncate here
|
||||||
|
|||||||
@@ -68,11 +68,12 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
// youtube
|
// youtube
|
||||||
if (urlp.hostname === 'youtu.be') {
|
if (urlp.hostname === 'youtu.be') {
|
||||||
const videoId = urlp.pathname.split('/')[1]
|
const videoId = urlp.pathname.split('/')[1]
|
||||||
|
const seek = encodeURIComponent(urlp.searchParams.get('t') ?? 0)
|
||||||
if (videoId) {
|
if (videoId) {
|
||||||
return {
|
return {
|
||||||
type: 'youtube_video',
|
type: 'youtube_video',
|
||||||
source: 'youtube',
|
source: 'youtube',
|
||||||
playerUri: `https://www.youtube.com/embed/${videoId}?autoplay=1&playsinline=1`,
|
playerUri: `https://www.youtube.com/embed/${videoId}?autoplay=1&playsinline=1&start=${seek}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,13 +85,14 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
const [_, page, shortVideoId] = urlp.pathname.split('/')
|
const [_, page, shortVideoId] = urlp.pathname.split('/')
|
||||||
const videoId =
|
const videoId =
|
||||||
page === 'shorts' ? shortVideoId : (urlp.searchParams.get('v') as string)
|
page === 'shorts' ? shortVideoId : (urlp.searchParams.get('v') as string)
|
||||||
|
const seek = encodeURIComponent(urlp.searchParams.get('t') ?? 0)
|
||||||
|
|
||||||
if (videoId) {
|
if (videoId) {
|
||||||
return {
|
return {
|
||||||
type: page === 'shorts' ? 'youtube_short' : 'youtube_video',
|
type: page === 'shorts' ? 'youtube_short' : 'youtube_video',
|
||||||
source: page === 'shorts' ? 'youtubeShorts' : 'youtube',
|
source: page === 'shorts' ? 'youtubeShorts' : 'youtube',
|
||||||
hideDetails: page === 'shorts' ? true : undefined,
|
hideDetails: page === 'shorts' ? true : undefined,
|
||||||
playerUri: `https://www.youtube.com/embed/${videoId}?autoplay=1&playsinline=1`,
|
playerUri: `https://www.youtube.com/embed/${videoId}?autoplay=1&playsinline=1&start=${seek}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-54
@@ -2,30 +2,32 @@ import {Platform} from 'react-native'
|
|||||||
import type {Theme} from './ThemeContext'
|
import type {Theme} from './ThemeContext'
|
||||||
import {colors} from './styles'
|
import {colors} from './styles'
|
||||||
|
|
||||||
|
import {darkPalette, lightPalette} from '#/alf/themes'
|
||||||
|
|
||||||
export const defaultTheme: Theme = {
|
export const defaultTheme: Theme = {
|
||||||
colorScheme: 'light',
|
colorScheme: 'light',
|
||||||
palette: {
|
palette: {
|
||||||
default: {
|
default: {
|
||||||
background: colors.white,
|
background: lightPalette.white,
|
||||||
backgroundLight: colors.gray1,
|
backgroundLight: lightPalette.contrast_50,
|
||||||
text: colors.black,
|
text: lightPalette.black,
|
||||||
textLight: colors.gray5,
|
textLight: lightPalette.contrast_700,
|
||||||
textInverted: colors.white,
|
textInverted: lightPalette.white,
|
||||||
link: colors.blue3,
|
link: lightPalette.primary_500,
|
||||||
border: '#f0e9e9',
|
border: lightPalette.contrast_100,
|
||||||
borderDark: '#e0d9d9',
|
borderDark: lightPalette.contrast_200,
|
||||||
icon: colors.gray4,
|
icon: lightPalette.contrast_500,
|
||||||
|
|
||||||
// non-standard
|
// non-standard
|
||||||
textVeryLight: colors.gray4,
|
textVeryLight: lightPalette.contrast_400,
|
||||||
replyLine: colors.gray2,
|
replyLine: lightPalette.contrast_100,
|
||||||
replyLineDot: colors.gray3,
|
replyLineDot: lightPalette.contrast_200,
|
||||||
unreadNotifBg: '#ebf6ff',
|
unreadNotifBg: lightPalette.primary_25,
|
||||||
unreadNotifBorder: colors.blue1,
|
unreadNotifBorder: lightPalette.primary_100,
|
||||||
postCtrl: '#71768A',
|
postCtrl: lightPalette.contrast_500,
|
||||||
brandText: '#0066FF',
|
brandText: lightPalette.primary_500,
|
||||||
emptyStateIcon: '#B6B6C9',
|
emptyStateIcon: lightPalette.contrast_300,
|
||||||
borderLinkHover: '#cac1c1',
|
borderLinkHover: lightPalette.contrast_300,
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
background: colors.blue3,
|
background: colors.blue3,
|
||||||
@@ -50,15 +52,15 @@ export const defaultTheme: Theme = {
|
|||||||
icon: colors.green4,
|
icon: colors.green4,
|
||||||
},
|
},
|
||||||
inverted: {
|
inverted: {
|
||||||
background: colors.black,
|
background: darkPalette.black,
|
||||||
backgroundLight: colors.gray6,
|
backgroundLight: darkPalette.contrast_50,
|
||||||
text: colors.white,
|
text: darkPalette.white,
|
||||||
textLight: colors.gray3,
|
textLight: darkPalette.contrast_700,
|
||||||
textInverted: colors.black,
|
textInverted: darkPalette.black,
|
||||||
link: colors.blue2,
|
link: darkPalette.primary_500,
|
||||||
border: colors.gray3,
|
border: darkPalette.contrast_100,
|
||||||
borderDark: colors.gray2,
|
borderDark: darkPalette.contrast_200,
|
||||||
icon: colors.gray5,
|
icon: darkPalette.contrast_500,
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
background: colors.red3,
|
background: colors.red3,
|
||||||
@@ -292,26 +294,26 @@ export const darkTheme: Theme = {
|
|||||||
palette: {
|
palette: {
|
||||||
...defaultTheme.palette,
|
...defaultTheme.palette,
|
||||||
default: {
|
default: {
|
||||||
background: colors.black,
|
background: darkPalette.black,
|
||||||
backgroundLight: colors.gray7,
|
backgroundLight: darkPalette.contrast_50,
|
||||||
text: colors.white,
|
text: darkPalette.white,
|
||||||
textLight: colors.gray3,
|
textLight: darkPalette.contrast_700,
|
||||||
textInverted: colors.black,
|
textInverted: darkPalette.black,
|
||||||
link: colors.blue3,
|
link: darkPalette.primary_500,
|
||||||
border: colors.gray7,
|
border: darkPalette.contrast_100,
|
||||||
borderDark: colors.gray6,
|
borderDark: darkPalette.contrast_200,
|
||||||
icon: colors.gray4,
|
icon: darkPalette.contrast_500,
|
||||||
|
|
||||||
// non-standard
|
// non-standard
|
||||||
textVeryLight: colors.gray4,
|
textVeryLight: darkPalette.contrast_400,
|
||||||
replyLine: colors.gray5,
|
replyLine: darkPalette.contrast_100,
|
||||||
replyLineDot: colors.gray6,
|
replyLineDot: darkPalette.contrast_200,
|
||||||
unreadNotifBg: colors.blue7,
|
unreadNotifBg: darkPalette.primary_975,
|
||||||
unreadNotifBorder: colors.blue6,
|
unreadNotifBorder: darkPalette.primary_900,
|
||||||
postCtrl: '#707489',
|
postCtrl: darkPalette.contrast_500,
|
||||||
brandText: '#0085ff',
|
brandText: darkPalette.primary_500,
|
||||||
emptyStateIcon: colors.gray4,
|
emptyStateIcon: darkPalette.contrast_300,
|
||||||
borderLinkHover: colors.gray5,
|
borderLinkHover: darkPalette.contrast_300,
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
...defaultTheme.palette.primary,
|
...defaultTheme.palette.primary,
|
||||||
@@ -322,15 +324,15 @@ export const darkTheme: Theme = {
|
|||||||
textInverted: colors.green2,
|
textInverted: colors.green2,
|
||||||
},
|
},
|
||||||
inverted: {
|
inverted: {
|
||||||
background: colors.white,
|
background: lightPalette.white,
|
||||||
backgroundLight: colors.gray2,
|
backgroundLight: lightPalette.contrast_50,
|
||||||
text: colors.black,
|
text: lightPalette.black,
|
||||||
textLight: colors.gray5,
|
textLight: lightPalette.contrast_700,
|
||||||
textInverted: colors.white,
|
textInverted: lightPalette.white,
|
||||||
link: colors.blue3,
|
link: lightPalette.primary_500,
|
||||||
border: colors.gray3,
|
border: lightPalette.contrast_100,
|
||||||
borderDark: colors.gray4,
|
borderDark: lightPalette.contrast_200,
|
||||||
icon: colors.gray1,
|
icon: lightPalette.contrast_500,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
|||||||
return AppLanguage.pt_BR
|
return AppLanguage.pt_BR
|
||||||
case 'uk':
|
case 'uk':
|
||||||
return AppLanguage.uk
|
return AppLanguage.uk
|
||||||
|
case 'ca':
|
||||||
|
return AppLanguage.ca
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {messages as messagesJa} from '#/locale/locales/ja/messages'
|
|||||||
import {messages as messagesKo} from '#/locale/locales/ko/messages'
|
import {messages as messagesKo} from '#/locale/locales/ko/messages'
|
||||||
import {messages as messagesPt_BR} from '#/locale/locales/pt-BR/messages'
|
import {messages as messagesPt_BR} from '#/locale/locales/pt-BR/messages'
|
||||||
import {messages as messagesUk} from '#/locale/locales/uk/messages'
|
import {messages as messagesUk} from '#/locale/locales/uk/messages'
|
||||||
|
import {messages as messagesCa} from '#/locale/locales/ca/messages'
|
||||||
|
|
||||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||||
import {AppLanguage} from '#/locale/languages'
|
import {AppLanguage} from '#/locale/languages'
|
||||||
@@ -59,6 +60,10 @@ export async function dynamicActivate(locale: AppLanguage) {
|
|||||||
i18n.loadAndActivate({locale, messages: messagesUk})
|
i18n.loadAndActivate({locale, messages: messagesUk})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case AppLanguage.ca: {
|
||||||
|
i18n.loadAndActivate({locale, messages: messagesCa})
|
||||||
|
break
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
i18n.loadAndActivate({locale, messages: messagesEn})
|
i18n.loadAndActivate({locale, messages: messagesEn})
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ export async function dynamicActivate(locale: AppLanguage) {
|
|||||||
mod = await import(`./locales/uk/messages`)
|
mod = await import(`./locales/uk/messages`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case AppLanguage.ca: {
|
||||||
|
mod = await import(`./locales/ca/messages`)
|
||||||
|
break
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
mod = await import(`./locales/en/messages`)
|
mod = await import(`./locales/en/messages`)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export enum AppLanguage {
|
|||||||
ko = 'ko',
|
ko = 'ko',
|
||||||
pt_BR = 'pt-BR',
|
pt_BR = 'pt-BR',
|
||||||
uk = 'uk',
|
uk = 'uk',
|
||||||
|
ca = 'ca',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AppLanguageConfig {
|
interface AppLanguageConfig {
|
||||||
@@ -35,6 +36,7 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [
|
|||||||
{code2: AppLanguage.ko, name: '한국어'},
|
{code2: AppLanguage.ko, name: '한국어'},
|
||||||
{code2: AppLanguage.pt_BR, name: 'Português (BR)'},
|
{code2: AppLanguage.pt_BR, name: 'Português (BR)'},
|
||||||
{code2: AppLanguage.uk, name: 'Українська'},
|
{code2: AppLanguage.uk, name: 'Українська'},
|
||||||
|
{code2: AppLanguage.ca, name: 'Catalan'},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const LANGUAGES: Language[] = [
|
export const LANGUAGES: Language[] = [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -345,11 +345,6 @@ msgstr ""
|
|||||||
msgid "Artistic or non-erotic nudity."
|
msgid "Artistic or non-erotic nudity."
|
||||||
msgstr "Künstlerische oder nicht-erotische Nacktheit."
|
msgstr "Künstlerische oder nicht-erotische Nacktheit."
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -364,6 +359,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Zurück"
|
msgstr "Zurück"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "Grundlagen"
|
msgstr "Grundlagen"
|
||||||
@@ -398,6 +398,7 @@ msgstr "Diese Kontos sperren?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -500,15 +501,6 @@ msgstr "Kamera"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "Darf nur Buchstaben, Zahlen, Leerzeichen, Bindestriche und Unterstriche enthalten. Muss mindestens 4 Zeichen lang sein, darf aber nicht länger als 32 Zeichen sein."
|
msgstr "Darf nur Buchstaben, Zahlen, Leerzeichen, Bindestriche und Unterstriche enthalten. Muss mindestens 4 Zeichen lang sein, darf aber nicht länger als 32 Zeichen sein."
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -527,8 +519,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Abbrechen"
|
msgstr "Abbrechen"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "Kontolöschung abbrechen"
|
msgstr "Kontolöschung abbrechen"
|
||||||
|
|
||||||
@@ -704,12 +705,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "Antwort zusammenstellen"
|
msgstr "Antwort zusammenstellen"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -719,6 +714,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "Bestätigen"
|
msgstr "Bestätigen"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -728,7 +729,7 @@ msgstr "Änderung bestätigen"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "Bestätigen Sie die Spracheinstellungen für den Inhalt"
|
msgstr "Bestätigen Sie die Spracheinstellungen für den Inhalt"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "Bestätigen Sie Konto löschen"
|
msgstr "Bestätigen Sie Konto löschen"
|
||||||
|
|
||||||
@@ -920,7 +921,7 @@ msgstr "App-Passwort löschen"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "Liste löschen"
|
msgstr "Liste löschen"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "Mein Konto löschen"
|
msgstr "Mein Konto löschen"
|
||||||
|
|
||||||
@@ -1624,7 +1625,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2014,6 +2015,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "Thema stummschalten"
|
msgstr "Thema stummschalten"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "Stumme Kontos"
|
msgstr "Stumme Kontos"
|
||||||
@@ -2124,11 +2129,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2139,6 +2139,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "Nächster"
|
msgstr "Nächster"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "Nächstes Bild "
|
msgstr "Nächstes Bild "
|
||||||
@@ -2395,7 +2400,7 @@ msgstr "Seite nicht gefunden"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Passwort"
|
msgstr "Passwort"
|
||||||
|
|
||||||
@@ -2473,7 +2478,7 @@ msgstr "Bitte geben Sie einen eindeutigen Namen für dieses App-Passwort ein ode
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr "Bitte geben Sie Ihre E-Mail-Adresse ein."
|
msgstr "Bitte geben Sie Ihre E-Mail-Adresse ein."
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "Bitte geben Sie auch Ihr Passwort ein:"
|
msgstr "Bitte geben Sie auch Ihr Passwort ein:"
|
||||||
|
|
||||||
@@ -3315,8 +3320,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "Abonnieren Sie diese Liste"
|
msgstr "Abonnieren Sie diese Liste"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3450,7 +3455,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3628,16 +3633,16 @@ msgstr "Stummschaltliste aufheben"
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "Sie können Ihren Dienst nicht kontaktieren. Bitte überprüfen Sie Ihre Internetverbindung."
|
msgstr "Sie können Ihren Dienst nicht kontaktieren. Bitte überprüfen Sie Ihre Internetverbindung."
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "Freischalten"
|
msgstr "Freischalten"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -3847,6 +3852,10 @@ msgstr "Seite besuchen"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -373,11 +373,6 @@ msgstr ""
|
|||||||
#~ msgid "Ask apps to limit the visibility of my account"
|
#~ msgid "Ask apps to limit the visibility of my account"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -392,6 +387,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -426,6 +426,7 @@ msgstr ""
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -528,15 +529,6 @@ msgstr ""
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -555,8 +547,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -736,12 +737,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -751,6 +746,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -760,7 +761,7 @@ msgstr ""
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -952,7 +953,7 @@ msgstr ""
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1673,7 +1674,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2075,6 +2076,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2189,11 +2194,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2204,6 +2204,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2477,7 +2482,7 @@ msgstr ""
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2555,7 +2560,7 @@ msgstr ""
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3422,8 +3427,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3557,7 +3562,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3735,13 +3740,13 @@ msgstr ""
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
msgctxt "action"
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
msgctxt "action"
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3958,6 +3963,10 @@ msgstr ""
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -341,11 +341,6 @@ msgstr ""
|
|||||||
msgid "Artistic or non-erotic nudity."
|
msgid "Artistic or non-erotic nudity."
|
||||||
msgstr "Desnudez artística o no erótica."
|
msgstr "Desnudez artística o no erótica."
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -360,6 +355,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Regresar"
|
msgstr "Regresar"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "Conceptos básicos"
|
msgstr "Conceptos básicos"
|
||||||
@@ -394,6 +394,7 @@ msgstr "¿Bloquear estas cuentas?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -496,15 +497,6 @@ msgstr "Cámara"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "Sólo puede contener letras, números, espacios, guiones y guiones bajos. Debe tener al menos 4 caracteres, pero no más de 32."
|
msgstr "Sólo puede contener letras, números, espacios, guiones y guiones bajos. Debe tener al menos 4 caracteres, pero no más de 32."
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -523,8 +515,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Cancelar"
|
msgstr "Cancelar"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "Cancelar la eliminación de la cuenta"
|
msgstr "Cancelar la eliminación de la cuenta"
|
||||||
|
|
||||||
@@ -700,12 +701,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "Redactar la respuesta"
|
msgstr "Redactar la respuesta"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -715,6 +710,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "Confirmar"
|
msgstr "Confirmar"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -724,7 +725,7 @@ msgstr "Confirmar el cambio"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "Confirmar la configuración del idioma del contenido"
|
msgstr "Confirmar la configuración del idioma del contenido"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "Confirmar eliminación de cuenta"
|
msgstr "Confirmar eliminación de cuenta"
|
||||||
|
|
||||||
@@ -916,7 +917,7 @@ msgstr "Borrar la contraseña de la app"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "Borrar la lista"
|
msgstr "Borrar la lista"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "Borrar mi cuenta"
|
msgstr "Borrar mi cuenta"
|
||||||
|
|
||||||
@@ -1620,7 +1621,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2006,6 +2007,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "Silenciar el hilo"
|
msgstr "Silenciar el hilo"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "Cuentas silenciadas"
|
msgstr "Cuentas silenciadas"
|
||||||
@@ -2116,11 +2121,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2131,6 +2131,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "Siguiente"
|
msgstr "Siguiente"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "Imagen nueva"
|
msgstr "Imagen nueva"
|
||||||
@@ -2387,7 +2392,7 @@ msgstr "Página no encontrada"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Contraseña"
|
msgstr "Contraseña"
|
||||||
|
|
||||||
@@ -2465,7 +2470,7 @@ msgstr "Introduce un nombre único para la contraseña de esta app o utiliza una
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr "Introduce tu correo electrónico."
|
msgstr "Introduce tu correo electrónico."
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "Introduce tu contraseña, también:"
|
msgstr "Introduce tu contraseña, también:"
|
||||||
|
|
||||||
@@ -3302,8 +3307,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "Suscribirse a esta lista"
|
msgstr "Suscribirse a esta lista"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3437,7 +3442,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3611,16 +3616,16 @@ msgstr "Desactivar la opción de silenciar la lista"
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "No se puede contactar con tu servicio. Comprueba tu conexión a Internet."
|
msgstr "No se puede contactar con tu servicio. Comprueba tu conexión a Internet."
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "Desbloquear"
|
msgstr "Desbloquear"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -3830,6 +3835,10 @@ msgstr "Visitar el sitio"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
+628
-595
File diff suppressed because it is too large
Load Diff
@@ -373,11 +373,6 @@ msgstr "कलात्मक या गैर-कामुक नग्नत
|
|||||||
#~ msgid "Ask apps to limit the visibility of my account"
|
#~ msgid "Ask apps to limit the visibility of my account"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -392,6 +387,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "वापस"
|
msgstr "वापस"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "मूल बातें"
|
msgstr "मूल बातें"
|
||||||
@@ -426,6 +426,7 @@ msgstr "खाता ब्लॉक करें?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -528,15 +529,6 @@ msgstr "कैमरा"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "केवल अक्षर, संख्या, रिक्त स्थान, डैश और अंडरस्कोर हो सकते हैं। कम से कम 4 अक्षर लंबा होना चाहिए, लेकिन 32 अक्षरों से अधिक लंबा नहीं होना चाहिए।।"
|
msgstr "केवल अक्षर, संख्या, रिक्त स्थान, डैश और अंडरस्कोर हो सकते हैं। कम से कम 4 अक्षर लंबा होना चाहिए, लेकिन 32 अक्षरों से अधिक लंबा नहीं होना चाहिए।।"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -555,8 +547,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "कैंसिल"
|
msgstr "कैंसिल"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "अकाउंट बंद मत करो"
|
msgstr "अकाउंट बंद मत करो"
|
||||||
|
|
||||||
@@ -732,12 +733,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "जवाब लिखो"
|
msgstr "जवाब लिखो"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -747,6 +742,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "हो गया"
|
msgstr "हो गया"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -756,7 +757,7 @@ msgstr "बदलाव की पुष्टि करें"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "सामग्री भाषा सेटिंग्स की पुष्टि करें"
|
msgstr "सामग्री भाषा सेटिंग्स की पुष्टि करें"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "खाते को हटा दें"
|
msgstr "खाते को हटा दें"
|
||||||
|
|
||||||
@@ -948,7 +949,7 @@ msgstr "अप्प पासवर्ड हटाएं"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "सूची हटाएँ"
|
msgstr "सूची हटाएँ"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "मेरा खाता हटाएं"
|
msgstr "मेरा खाता हटाएं"
|
||||||
|
|
||||||
@@ -1665,7 +1666,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2067,6 +2068,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "थ्रेड म्यूट करें"
|
msgstr "थ्रेड म्यूट करें"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "म्यूट किए गए खाते"
|
msgstr "म्यूट किए गए खाते"
|
||||||
@@ -2181,11 +2186,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2196,6 +2196,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "अगला"
|
msgstr "अगला"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "अगली फोटो"
|
msgstr "अगली फोटो"
|
||||||
@@ -2469,7 +2474,7 @@ msgstr "पृष्ठ नहीं मिला"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "पासवर्ड"
|
msgstr "पासवर्ड"
|
||||||
|
|
||||||
@@ -2547,7 +2552,7 @@ msgstr "कृपया इस ऐप पासवर्ड के लिए ए
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "कृपया अपना पासवर्ड भी दर्ज करें:"
|
msgstr "कृपया अपना पासवर्ड भी दर्ज करें:"
|
||||||
|
|
||||||
@@ -3414,8 +3419,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "इस सूची को सब्सक्राइब करें"
|
msgstr "इस सूची को सब्सक्राइब करें"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3549,7 +3554,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3727,16 +3732,16 @@ msgstr ""
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "आपकी सेवा से संपर्क करने में असमर्थ। कृपया अपने इंटरनेट कनेक्शन की जांच करें।।"
|
msgstr "आपकी सेवा से संपर्क करने में असमर्थ। कृपया अपने इंटरनेट कनेक्शन की जांच करें।।"
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "अनब्लॉक"
|
msgstr "अनब्लॉक"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -3950,6 +3955,10 @@ msgstr "साइट पर जाएं"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -345,11 +345,6 @@ msgstr ""
|
|||||||
msgid "Artistic or non-erotic nudity."
|
msgid "Artistic or non-erotic nudity."
|
||||||
msgstr "Ketelanjangan artistik atau non-erotis."
|
msgstr "Ketelanjangan artistik atau non-erotis."
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -364,6 +359,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Kembali"
|
msgstr "Kembali"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "Dasar"
|
msgstr "Dasar"
|
||||||
@@ -398,6 +398,7 @@ msgstr "Blokir akun ini?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -500,15 +501,6 @@ msgstr "Kamera"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "Hanya dapat terdiri dari huruf, angka, spasi, tanda hubung dan garis bawah. Minimal 4 karakter, namun tidak boleh lebih dari 32 karakter."
|
msgstr "Hanya dapat terdiri dari huruf, angka, spasi, tanda hubung dan garis bawah. Minimal 4 karakter, namun tidak boleh lebih dari 32 karakter."
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -527,8 +519,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Batal"
|
msgstr "Batal"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "Batal menghapus akun"
|
msgstr "Batal menghapus akun"
|
||||||
|
|
||||||
@@ -704,12 +705,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "Tulis balasan"
|
msgstr "Tulis balasan"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -719,6 +714,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "Konfirmasi"
|
msgstr "Konfirmasi"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -728,7 +729,7 @@ msgstr "Konfirmasi Perubahan"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "Konfirmasi pengaturan bahasa konten"
|
msgstr "Konfirmasi pengaturan bahasa konten"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "Konfirmasi hapus akun"
|
msgstr "Konfirmasi hapus akun"
|
||||||
|
|
||||||
@@ -920,7 +921,7 @@ msgstr "Hapus kata sandi aplikasi"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "Hapus Daftar "
|
msgstr "Hapus Daftar "
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "Hapus akun saya"
|
msgstr "Hapus akun saya"
|
||||||
|
|
||||||
@@ -1624,7 +1625,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2014,6 +2015,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "Bisukan utasan"
|
msgstr "Bisukan utasan"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "Akun yang dibisukan"
|
msgstr "Akun yang dibisukan"
|
||||||
@@ -2124,11 +2129,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2139,6 +2139,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "Berikutnya"
|
msgstr "Berikutnya"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "Gambar berikutnya"
|
msgstr "Gambar berikutnya"
|
||||||
@@ -2395,7 +2400,7 @@ msgstr "Halaman tidak ditemukan"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Kata sandi"
|
msgstr "Kata sandi"
|
||||||
|
|
||||||
@@ -2473,7 +2478,7 @@ msgstr "Mohon masukkan nama unik untuk Kata Sandi Aplikasi ini atau gunakan nama
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr "Masukkan email Anda."
|
msgstr "Masukkan email Anda."
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "Masukkan juga kata sandi Anda:"
|
msgstr "Masukkan juga kata sandi Anda:"
|
||||||
|
|
||||||
@@ -3315,8 +3320,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "Langganan ke daftar ini"
|
msgstr "Langganan ke daftar ini"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3450,7 +3455,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3628,16 +3633,16 @@ msgstr "Bunyikan daftar"
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "Tidak dapat terhubung ke layanan. Mohon periksa koneksi internet Anda."
|
msgstr "Tidak dapat terhubung ke layanan. Mohon periksa koneksi internet Anda."
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "Buka blokir"
|
msgstr "Buka blokir"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -3847,6 +3852,10 @@ msgstr "Kunjungi Halaman"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -345,11 +345,6 @@ msgstr ""
|
|||||||
msgid "Artistic or non-erotic nudity."
|
msgid "Artistic or non-erotic nudity."
|
||||||
msgstr "芸術的または性的ではないヌード。"
|
msgstr "芸術的または性的ではないヌード。"
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -364,6 +359,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "戻る"
|
msgstr "戻る"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "基本"
|
msgstr "基本"
|
||||||
@@ -398,6 +398,7 @@ msgstr "これらのアカウントをブロックしますか?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -500,15 +501,6 @@ msgstr "カメラ"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "文字、数字、スペース、ハイフン、およびアンダースコアのみが使用可能です。長さは4文字以上32文字以下である必要があります。"
|
msgstr "文字、数字、スペース、ハイフン、およびアンダースコアのみが使用可能です。長さは4文字以上32文字以下である必要があります。"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -527,8 +519,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "キャンセル"
|
msgstr "キャンセル"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "アカウントの削除をキャンセル"
|
msgstr "アカウントの削除をキャンセル"
|
||||||
|
|
||||||
@@ -704,12 +705,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "返信を作成"
|
msgstr "返信を作成"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -719,6 +714,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "確認"
|
msgstr "確認"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -728,7 +729,7 @@ msgstr "変更を確認"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "コンテンツの言語設定を確認"
|
msgstr "コンテンツの言語設定を確認"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "アカウントの削除を確認"
|
msgstr "アカウントの削除を確認"
|
||||||
|
|
||||||
@@ -920,7 +921,7 @@ msgstr "アプリパスワードを削除"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "リストを削除"
|
msgstr "リストを削除"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "マイアカウントを削除"
|
msgstr "マイアカウントを削除"
|
||||||
|
|
||||||
@@ -1624,7 +1625,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2022,6 +2023,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "スレッドをミュート"
|
msgstr "スレッドをミュート"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "ミュート中のアカウント"
|
msgstr "ミュート中のアカウント"
|
||||||
@@ -2132,11 +2137,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr "新しい順に返信を表示"
|
msgstr "新しい順に返信を表示"
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2147,6 +2147,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "次へ"
|
msgstr "次へ"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "次の画像"
|
msgstr "次の画像"
|
||||||
@@ -2407,7 +2412,7 @@ msgstr "ページが見つかりません"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "パスワード"
|
msgstr "パスワード"
|
||||||
|
|
||||||
@@ -2485,7 +2490,7 @@ msgstr "このアプリパスワードに固有の名前を入力するか、ラ
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr "メールアドレスを入力してください。"
|
msgstr "メールアドレスを入力してください。"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "パスワードも入力してください:"
|
msgstr "パスワードも入力してください:"
|
||||||
|
|
||||||
@@ -3331,8 +3336,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "このリストに登録"
|
msgstr "このリストに登録"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3466,7 +3471,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3644,16 +3649,16 @@ msgstr "リストでのミュートを解除"
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "あなたのサービスに接続できません。インターネットの接続を確認してください。"
|
msgstr "あなたのサービスに接続できません。インターネットの接続を確認してください。"
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "ブロックを解除"
|
msgstr "ブロックを解除"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -3863,6 +3868,10 @@ msgstr "サイトへアクセス"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -421,11 +421,6 @@ msgstr "선정적이지 않거나 예술적인 노출."
|
|||||||
#~ msgid "Ask apps to limit the visibility of my account"
|
#~ msgid "Ask apps to limit the visibility of my account"
|
||||||
#~ msgstr "내 계정의 공개 범위를 제한하도록 앱에 요청하기"
|
#~ msgstr "내 계정의 공개 범위를 제한하도록 앱에 요청하기"
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -440,6 +435,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "뒤로"
|
msgstr "뒤로"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "기본"
|
msgstr "기본"
|
||||||
@@ -474,6 +474,7 @@ msgstr "이 계정들을 차단하시겠습니까?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -580,15 +581,6 @@ msgstr "카메라"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "글자, 숫자, 공백, 대시, 밑줄만 포함할 수 있습니다. 길이는 4자 이상이어야 하고 32자를 넘지 않아야 합니다."
|
msgstr "글자, 숫자, 공백, 대시, 밑줄만 포함할 수 있습니다. 길이는 4자 이상이어야 하고 32자를 넘지 않아야 합니다."
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -607,8 +599,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "취소"
|
msgstr "취소"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "계정 삭제 취소"
|
msgstr "계정 삭제 취소"
|
||||||
|
|
||||||
@@ -788,12 +789,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "답글 작성하기"
|
msgstr "답글 작성하기"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -803,6 +798,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "확인"
|
msgstr "확인"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -812,7 +813,7 @@ msgstr "변경 확인"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "콘텐츠 언어 설정 확인"
|
msgstr "콘텐츠 언어 설정 확인"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "계정 삭제 확인"
|
msgstr "계정 삭제 확인"
|
||||||
|
|
||||||
@@ -1020,7 +1021,7 @@ msgstr "앱 비밀번호 삭제"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "리스트 삭제"
|
msgstr "리스트 삭제"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "내 계정 삭제"
|
msgstr "내 계정 삭제"
|
||||||
|
|
||||||
@@ -1850,7 +1851,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2256,6 +2257,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "스레드 뮤트"
|
msgstr "스레드 뮤트"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "뮤트한 계정"
|
msgstr "뮤트한 계정"
|
||||||
@@ -2379,11 +2384,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr "새로운 순"
|
msgstr "새로운 순"
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2394,6 +2394,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "다음"
|
msgstr "다음"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "다음 이미지"
|
msgstr "다음 이미지"
|
||||||
@@ -2688,7 +2693,7 @@ msgstr "페이지를 찾을 수 없음"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "비밀번호"
|
msgstr "비밀번호"
|
||||||
|
|
||||||
@@ -2766,7 +2771,7 @@ msgstr "이 앱 비밀번호에 대해 고유한 이름을 입력하거나 무
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr "이메일을 입력하세요."
|
msgstr "이메일을 입력하세요."
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "비밀번호도 입력해 주세요:"
|
msgstr "비밀번호도 입력해 주세요:"
|
||||||
|
|
||||||
@@ -3683,8 +3688,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "이 리스트로 구독"
|
msgstr "이 리스트로 구독"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3839,7 +3844,7 @@ msgstr "서버에 연결하는 동안 문제가 발생했습니다."
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4029,16 +4034,16 @@ msgstr "리스트 언뮤트"
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "서비스에 연결할 수 없습니다. 인터넷 연결을 확인하세요."
|
msgstr "서비스에 연결할 수 없습니다. 인터넷 연결을 확인하세요."
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "차단 해제"
|
msgstr "차단 해제"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -4290,6 +4295,10 @@ msgstr "사이트 방문"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr "경고"
|
msgstr "경고"
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr "이의신청을 즉시 검토하겠습니다."
|
msgstr "이의신청을 즉시 검토하겠습니다."
|
||||||
|
|||||||
@@ -373,11 +373,6 @@ msgstr "Nudez artística ou não erótica."
|
|||||||
#~ msgid "Ask apps to limit the visibility of my account"
|
#~ msgid "Ask apps to limit the visibility of my account"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -392,6 +387,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Voltar"
|
msgstr "Voltar"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "Básicos"
|
msgstr "Básicos"
|
||||||
@@ -426,6 +426,7 @@ msgstr "Bloquear esta conta?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -528,15 +529,6 @@ msgstr "Câmera"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "Só pode conter letras, números, espaços, traços e sublinhados. Deve ter pelo menos 4 caracteres, mas não mais de 32 caracteres."
|
msgstr "Só pode conter letras, números, espaços, traços e sublinhados. Deve ter pelo menos 4 caracteres, mas não mais de 32 caracteres."
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -555,8 +547,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Cancelar"
|
msgstr "Cancelar"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "Cancelar exclusão da conta"
|
msgstr "Cancelar exclusão da conta"
|
||||||
|
|
||||||
@@ -736,12 +737,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "Escrever resposta"
|
msgstr "Escrever resposta"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -751,6 +746,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "Confirme"
|
msgstr "Confirme"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -760,7 +761,7 @@ msgstr "Confirmar Alterações"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "Confirmar configurações de idioma de conteúdo"
|
msgstr "Confirmar configurações de idioma de conteúdo"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "Confirmar a exclusão da conta"
|
msgstr "Confirmar a exclusão da conta"
|
||||||
|
|
||||||
@@ -952,7 +953,7 @@ msgstr "Excluir senha do aplicativo"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "Excluir Lista"
|
msgstr "Excluir Lista"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "Excluir minha conta"
|
msgstr "Excluir minha conta"
|
||||||
|
|
||||||
@@ -1673,7 +1674,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2075,6 +2076,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "Silenciar tópico"
|
msgstr "Silenciar tópico"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "Contas silenciadas"
|
msgstr "Contas silenciadas"
|
||||||
@@ -2189,11 +2194,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr "Respostas mais recentes primeiro"
|
msgstr "Respostas mais recentes primeiro"
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2204,6 +2204,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "Próximo"
|
msgstr "Próximo"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "Próxima imagem"
|
msgstr "Próxima imagem"
|
||||||
@@ -2477,7 +2482,7 @@ msgstr "Página não encontrada"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Senha"
|
msgstr "Senha"
|
||||||
|
|
||||||
@@ -2555,7 +2560,7 @@ msgstr "Por favor, insira um nome único para esta Senha do Aplicativo ou use no
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr "Por favor, digite o seu email."
|
msgstr "Por favor, digite o seu email."
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "Por favor, digite sua senha também:"
|
msgstr "Por favor, digite sua senha também:"
|
||||||
|
|
||||||
@@ -3422,8 +3427,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "Assinar esta lista"
|
msgstr "Assinar esta lista"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3557,7 +3562,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3735,16 +3740,16 @@ msgstr "Lista de não silenciados"
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "Não foi possível entrar em contato com seu serviço. Por favor, verifique sua conexão à internet."
|
msgstr "Não foi possível entrar em contato com seu serviço. Por favor, verifique sua conexão à internet."
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "Desbloquear"
|
msgstr "Desbloquear"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -3958,6 +3963,10 @@ msgstr "Visitar Site"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -378,11 +378,6 @@ msgstr "Художня або нееротична оголеність."
|
|||||||
#~ msgid "Ask apps to limit the visibility of my account"
|
#~ msgid "Ask apps to limit the visibility of my account"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/com/post-thread/PostThread.tsx:400
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:141
|
#: src/view/com/auth/create/CreateAccount.tsx:141
|
||||||
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
#: src/view/com/auth/login/ChooseAccountForm.tsx:151
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:170
|
||||||
@@ -397,6 +392,11 @@ msgstr ""
|
|||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Назад"
|
msgstr "Назад"
|
||||||
|
|
||||||
|
#: src/view/com/post-thread/PostThread.tsx:400
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Settings.tsx:489
|
#: src/view/screens/Settings.tsx:489
|
||||||
msgid "Basics"
|
msgid "Basics"
|
||||||
msgstr "Основні"
|
msgstr "Основні"
|
||||||
@@ -431,6 +431,7 @@ msgstr "Заблокувати ці облікові записи?"
|
|||||||
msgid "Block this List"
|
msgid "Block this List"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:109
|
||||||
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:57
|
||||||
msgid "Blocked"
|
msgid "Blocked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -533,15 +534,6 @@ msgstr "Камера"
|
|||||||
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long."
|
||||||
msgstr "Може містити лише літери, цифри, пробіли, дефіси та знаки підкреслення, і мати довжину від 4 до 32 символів."
|
msgstr "Може містити лише літери, цифри, пробіли, дефіси та знаки підкреслення, і мати довжину від 4 до 32 символів."
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:88
|
|
||||||
#: src/view/com/modals/Confirm.tsx:91
|
|
||||||
#: src/view/com/modals/CreateOrEditList.tsx:293
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:152
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:227
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/composer/Composer.tsx:300
|
#: src/view/com/composer/Composer.tsx:300
|
||||||
#: src/view/com/composer/Composer.tsx:305
|
#: src/view/com/composer/Composer.tsx:305
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:218
|
#: src/view/com/modals/ChangeEmail.tsx:218
|
||||||
@@ -560,8 +552,17 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Скасувати"
|
msgstr "Скасувати"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:88
|
||||||
|
#: src/view/com/modals/Confirm.tsx:91
|
||||||
|
#: src/view/com/modals/CreateOrEditList.tsx:293
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:152
|
||||||
|
#: src/view/com/modals/DeleteAccount.tsx:230
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:148
|
#: src/view/com/modals/DeleteAccount.tsx:148
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:223
|
#: src/view/com/modals/DeleteAccount.tsx:226
|
||||||
msgid "Cancel account deletion"
|
msgid "Cancel account deletion"
|
||||||
msgstr "Скасувати видалення облікового запису"
|
msgstr "Скасувати видалення облікового запису"
|
||||||
|
|
||||||
@@ -741,12 +742,6 @@ msgstr ""
|
|||||||
msgid "Compose reply"
|
msgid "Compose reply"
|
||||||
msgstr "Відповісти"
|
msgstr "Відповісти"
|
||||||
|
|
||||||
#: src/view/com/modals/Confirm.tsx:75
|
|
||||||
#: src/view/com/modals/Confirm.tsx:78
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:98
|
#: src/view/com/modals/AppealLabel.tsx:98
|
||||||
#: src/view/com/modals/SelfLabel.tsx:154
|
#: src/view/com/modals/SelfLabel.tsx:154
|
||||||
#: src/view/com/modals/VerifyEmail.tsx:231
|
#: src/view/com/modals/VerifyEmail.tsx:231
|
||||||
@@ -756,6 +751,12 @@ msgstr ""
|
|||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "Підтвердити"
|
msgstr "Підтвердити"
|
||||||
|
|
||||||
|
#: src/view/com/modals/Confirm.tsx:75
|
||||||
|
#: src/view/com/modals/Confirm.tsx:78
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:193
|
#: src/view/com/modals/ChangeEmail.tsx:193
|
||||||
#: src/view/com/modals/ChangeEmail.tsx:195
|
#: src/view/com/modals/ChangeEmail.tsx:195
|
||||||
msgid "Confirm Change"
|
msgid "Confirm Change"
|
||||||
@@ -765,7 +766,7 @@ msgstr "Підтвердити"
|
|||||||
msgid "Confirm content language settings"
|
msgid "Confirm content language settings"
|
||||||
msgstr "Підтвердити перелік мов"
|
msgstr "Підтвердити перелік мов"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:213
|
#: src/view/com/modals/DeleteAccount.tsx:216
|
||||||
msgid "Confirm delete account"
|
msgid "Confirm delete account"
|
||||||
msgstr "Підтвердити видалення облікового запису"
|
msgstr "Підтвердити видалення облікового запису"
|
||||||
|
|
||||||
@@ -957,7 +958,7 @@ msgstr "Видалити пароль для застосунку"
|
|||||||
msgid "Delete List"
|
msgid "Delete List"
|
||||||
msgstr "Видалити список"
|
msgstr "Видалити список"
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:216
|
#: src/view/com/modals/DeleteAccount.tsx:219
|
||||||
msgid "Delete my account"
|
msgid "Delete my account"
|
||||||
msgstr "Видалити мій обліковий запис"
|
msgstr "Видалити мій обліковий запис"
|
||||||
|
|
||||||
@@ -1678,7 +1679,7 @@ msgstr ""
|
|||||||
msgid "Input new password"
|
msgid "Input new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:196
|
#: src/view/com/modals/DeleteAccount.tsx:199
|
||||||
msgid "Input password for account deletion"
|
msgid "Input password for account deletion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2080,6 +2081,10 @@ msgstr ""
|
|||||||
msgid "Mute thread"
|
msgid "Mute thread"
|
||||||
msgstr "Ігнорувати пост"
|
msgstr "Ігнорувати пост"
|
||||||
|
|
||||||
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
|
msgid "Muted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Moderation.tsx:109
|
#: src/view/screens/Moderation.tsx:109
|
||||||
msgid "Muted accounts"
|
msgid "Muted accounts"
|
||||||
msgstr "Ігноровані облікові записи"
|
msgstr "Ігноровані облікові записи"
|
||||||
@@ -2194,11 +2199,6 @@ msgstr ""
|
|||||||
msgid "Newest replies first"
|
msgid "Newest replies first"
|
||||||
msgstr "Спочатку найновіші"
|
msgstr "Спочатку найновіші"
|
||||||
|
|
||||||
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/auth/create/CreateAccount.tsx:154
|
#: src/view/com/auth/create/CreateAccount.tsx:154
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:178
|
||||||
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
#: src/view/com/auth/login/ForgotPasswordForm.tsx:188
|
||||||
@@ -2209,6 +2209,11 @@ msgstr ""
|
|||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "Далі"
|
msgstr "Далі"
|
||||||
|
|
||||||
|
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:103
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
#: src/view/com/lightbox/Lightbox.web.tsx:142
|
||||||
msgid "Next image"
|
msgid "Next image"
|
||||||
msgstr "Наступне зображення"
|
msgstr "Наступне зображення"
|
||||||
@@ -2482,7 +2487,7 @@ msgstr "Сторінку не знайдено"
|
|||||||
#: src/view/com/auth/create/Step2.tsx:132
|
#: src/view/com/auth/create/Step2.tsx:132
|
||||||
#: src/view/com/auth/login/LoginForm.tsx:223
|
#: src/view/com/auth/login/LoginForm.tsx:223
|
||||||
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
#: src/view/com/auth/login/SetNewPasswordForm.tsx:132
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:195
|
#: src/view/com/modals/DeleteAccount.tsx:198
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Пароль"
|
msgstr "Пароль"
|
||||||
|
|
||||||
@@ -2560,7 +2565,7 @@ msgstr "Будь ласка, введіть унікальну назву для
|
|||||||
msgid "Please enter your email."
|
msgid "Please enter your email."
|
||||||
msgstr "Будь ласка, введіть адресу ел. пошти."
|
msgstr "Будь ласка, введіть адресу ел. пошти."
|
||||||
|
|
||||||
#: src/view/com/modals/DeleteAccount.tsx:184
|
#: src/view/com/modals/DeleteAccount.tsx:187
|
||||||
msgid "Please enter your password as well:"
|
msgid "Please enter your password as well:"
|
||||||
msgstr "Будь ласка, також введіть ваш пароль:"
|
msgstr "Будь ласка, також введіть ваш пароль:"
|
||||||
|
|
||||||
@@ -3427,8 +3432,8 @@ msgid "Subscribe to this list"
|
|||||||
msgstr "Підписатися на цей список"
|
msgstr "Підписатися на цей список"
|
||||||
|
|
||||||
#: src/view/com/lists/ListCard.tsx:101
|
#: src/view/com/lists/ListCard.tsx:101
|
||||||
msgid "Subscribed"
|
#~ msgid "Subscribed"
|
||||||
msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
#: src/view/screens/Search/Search.tsx:362
|
#: src/view/screens/Search/Search.tsx:362
|
||||||
msgid "Suggested Follows"
|
msgid "Suggested Follows"
|
||||||
@@ -3562,7 +3567,7 @@ msgstr ""
|
|||||||
msgid "There was an issue fetching notifications. Tap here to try again."
|
msgid "There was an issue fetching notifications. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/posts/Feed.tsx:261
|
#: src/view/com/posts/Feed.tsx:263
|
||||||
msgid "There was an issue fetching posts. Tap here to try again."
|
msgid "There was an issue fetching posts. Tap here to try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3740,16 +3745,16 @@ msgstr "Перестати ігнорувати"
|
|||||||
msgid "Unable to contact your service. Please check your Internet connection."
|
msgid "Unable to contact your service. Please check your Internet connection."
|
||||||
msgstr "Не вдалося зв'язатися з вашим хостинг-провайдером. Перевірте ваше підключення до Інтернету."
|
msgstr "Не вдалося зв'язатися з вашим хостинг-провайдером. Перевірте ваше підключення до Інтернету."
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:475
|
|
||||||
msgctxt "action"
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:472
|
#: src/view/com/profile/ProfileHeader.tsx:472
|
||||||
#: src/view/screens/ProfileList.tsx:568
|
#: src/view/screens/ProfileList.tsx:568
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "Розблокувати"
|
msgstr "Розблокувати"
|
||||||
|
|
||||||
|
#: src/view/com/profile/ProfileHeader.tsx:475
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Unblock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:308
|
#: src/view/com/profile/ProfileHeader.tsx:308
|
||||||
#: src/view/com/profile/ProfileHeader.tsx:392
|
#: src/view/com/profile/ProfileHeader.tsx:392
|
||||||
msgid "Unblock Account"
|
msgid "Unblock Account"
|
||||||
@@ -3963,6 +3968,10 @@ msgstr "Відвідати сайт"
|
|||||||
msgid "Warn"
|
msgid "Warn"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/view/com/posts/DiscoverFallbackHeader.tsx:29
|
||||||
|
msgid "We ran out of posts from your follows. Here's the latest from"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/view/com/modals/AppealLabel.tsx:48
|
#: src/view/com/modals/AppealLabel.tsx:48
|
||||||
msgid "We'll look into your appeal promptly."
|
msgid "We'll look into your appeal promptly."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {DialogControlProps} from '#/components/Dialog'
|
||||||
|
|
||||||
|
const DialogContext = React.createContext<{
|
||||||
|
activeDialogs: React.MutableRefObject<
|
||||||
|
Map<string, React.MutableRefObject<DialogControlProps>>
|
||||||
|
>
|
||||||
|
}>({
|
||||||
|
activeDialogs: {
|
||||||
|
current: new Map(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const DialogControlContext = React.createContext<{
|
||||||
|
closeAllDialogs(): void
|
||||||
|
}>({
|
||||||
|
closeAllDialogs: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useDialogStateContext() {
|
||||||
|
return React.useContext(DialogContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDialogStateControlContext() {
|
||||||
|
return React.useContext(DialogControlContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const activeDialogs = React.useRef<
|
||||||
|
Map<string, React.MutableRefObject<DialogControlProps>>
|
||||||
|
>(new Map())
|
||||||
|
const closeAllDialogs = React.useCallback(() => {
|
||||||
|
activeDialogs.current.forEach(dialog => dialog.current.close())
|
||||||
|
}, [])
|
||||||
|
const context = React.useMemo(() => ({activeDialogs}), [])
|
||||||
|
const controls = React.useMemo(() => ({closeAllDialogs}), [closeAllDialogs])
|
||||||
|
return (
|
||||||
|
<DialogContext.Provider value={context}>
|
||||||
|
<DialogControlContext.Provider value={controls}>
|
||||||
|
{children}
|
||||||
|
</DialogControlContext.Provider>
|
||||||
|
</DialogContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ test('migrate: fresh install', async () => {
|
|||||||
|
|
||||||
expect(AsyncStorage.getItem).toHaveBeenCalledWith('root')
|
expect(AsyncStorage.getItem).toHaveBeenCalledWith('root')
|
||||||
expect(read).toHaveBeenCalledTimes(1)
|
expect(read).toHaveBeenCalledTimes(1)
|
||||||
expect(logger.log).toHaveBeenCalledWith(
|
expect(logger.info).toHaveBeenCalledWith(
|
||||||
'persisted state: no migration needed',
|
'persisted state: no migration needed',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -38,7 +38,7 @@ test('migrate: fresh install, existing new storage', async () => {
|
|||||||
|
|
||||||
expect(AsyncStorage.getItem).toHaveBeenCalledWith('root')
|
expect(AsyncStorage.getItem).toHaveBeenCalledWith('root')
|
||||||
expect(read).toHaveBeenCalledTimes(1)
|
expect(read).toHaveBeenCalledTimes(1)
|
||||||
expect(logger.log).toHaveBeenCalledWith(
|
expect(logger.info).toHaveBeenCalledWith(
|
||||||
'persisted state: no migration needed',
|
'persisted state: no migration needed',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -68,7 +68,7 @@ test('migrate: has legacy data', async () => {
|
|||||||
await migrate()
|
await migrate()
|
||||||
|
|
||||||
expect(write).toHaveBeenCalledWith(transform(fixtures.LEGACY_DATA_DUMP))
|
expect(write).toHaveBeenCalledWith(transform(fixtures.LEGACY_DATA_DUMP))
|
||||||
expect(logger.log).toHaveBeenCalledWith(
|
expect(logger.info).toHaveBeenCalledWith(
|
||||||
'persisted state: migrated legacy storage',
|
'persisted state: migrated legacy storage',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -164,14 +164,14 @@ export async function migrate() {
|
|||||||
|
|
||||||
if (validate.success) {
|
if (validate.success) {
|
||||||
await write(newData)
|
await write(newData)
|
||||||
logger.log('persisted state: migrated legacy storage')
|
logger.info('persisted state: migrated legacy storage')
|
||||||
} else {
|
} else {
|
||||||
logger.error('persisted state: legacy data failed validation', {
|
logger.error('persisted state: legacy data failed validation', {
|
||||||
error: validate.error,
|
error: validate.error,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.log('persisted state: no migration needed')
|
logger.info('persisted state: no migration needed')
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error(e, {
|
logger.error(e, {
|
||||||
|
|||||||
@@ -272,7 +272,8 @@ export function usePinnedFeedsInfos(): {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(`usePinnedFeedsInfos: failed to fetch ${uri}`, {
|
// expected failure
|
||||||
|
logger.info(`usePinnedFeedsInfos: failed to fetch ${uri}`, {
|
||||||
error: e,
|
error: e,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
}
|
}
|
||||||
broadcast.postMessage({event: unreadCountStr})
|
broadcast.postMessage({event: unreadCountStr})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Failed to check unread notifications', {error: e})
|
logger.warn('Failed to check unread notifications', {error: e})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {LikesFeedAPI} from 'lib/api/feed/likes'
|
|||||||
import {CustomFeedAPI} from 'lib/api/feed/custom'
|
import {CustomFeedAPI} from 'lib/api/feed/custom'
|
||||||
import {ListFeedAPI} from 'lib/api/feed/list'
|
import {ListFeedAPI} from 'lib/api/feed/list'
|
||||||
import {MergeFeedAPI} from 'lib/api/feed/merge'
|
import {MergeFeedAPI} from 'lib/api/feed/merge'
|
||||||
|
import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {precacheFeedPosts as precacheResolvedUris} from './resolve-uri'
|
import {precacheFeedPosts as precacheResolvedUris} from './resolve-uri'
|
||||||
@@ -338,7 +339,11 @@ function createApi(
|
|||||||
feedTuners: FeedTunerFn[],
|
feedTuners: FeedTunerFn[],
|
||||||
) {
|
) {
|
||||||
if (feedDesc === 'home') {
|
if (feedDesc === 'home') {
|
||||||
return new MergeFeedAPI(params, feedTuners)
|
if (params.mergeFeedEnabled) {
|
||||||
|
return new MergeFeedAPI(params, feedTuners)
|
||||||
|
} else {
|
||||||
|
return new HomeFeedAPI()
|
||||||
|
}
|
||||||
} else if (feedDesc === 'following') {
|
} else if (feedDesc === 'following') {
|
||||||
return new FollowingFeedAPI()
|
return new FollowingFeedAPI()
|
||||||
} else if (feedDesc.startsWith('author')) {
|
} else if (feedDesc.startsWith('author')) {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ export type ApiContext = {
|
|||||||
password: string
|
password: string
|
||||||
handle: string
|
handle: string
|
||||||
inviteCode?: string
|
inviteCode?: string
|
||||||
|
verificationPhone?: string
|
||||||
|
verificationCode?: string
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
login: (props: {
|
login: (props: {
|
||||||
service: string
|
service: string
|
||||||
@@ -203,7 +205,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
}, [setStateAndPersist, queryClient])
|
}, [setStateAndPersist, queryClient])
|
||||||
|
|
||||||
const createAccount = React.useCallback<ApiContext['createAccount']>(
|
const createAccount = React.useCallback<ApiContext['createAccount']>(
|
||||||
async ({service, email, password, handle, inviteCode}: any) => {
|
async ({
|
||||||
|
service,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
handle,
|
||||||
|
inviteCode,
|
||||||
|
verificationPhone,
|
||||||
|
verificationCode,
|
||||||
|
}: any) => {
|
||||||
logger.info(`session: creating account`, {
|
logger.info(`session: creating account`, {
|
||||||
service,
|
service,
|
||||||
handle,
|
handle,
|
||||||
@@ -217,6 +227,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
inviteCode,
|
inviteCode,
|
||||||
|
verificationPhone,
|
||||||
|
verificationCode,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!agent.session) {
|
if (!agent.session) {
|
||||||
|
|||||||
@@ -1,204 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {Pressable, Text, PressableProps, TextProps} from 'react-native'
|
|
||||||
import * as tokens from '#/alf/tokens'
|
|
||||||
import {atoms} from '#/alf'
|
|
||||||
|
|
||||||
export type ButtonType =
|
|
||||||
| 'primary'
|
|
||||||
| 'secondary'
|
|
||||||
| 'tertiary'
|
|
||||||
| 'positive'
|
|
||||||
| 'negative'
|
|
||||||
export type ButtonSize = 'small' | 'large'
|
|
||||||
|
|
||||||
export type VariantProps = {
|
|
||||||
type?: ButtonType
|
|
||||||
size?: ButtonSize
|
|
||||||
}
|
|
||||||
type ButtonState = {
|
|
||||||
pressed: boolean
|
|
||||||
hovered: boolean
|
|
||||||
focused: boolean
|
|
||||||
}
|
|
||||||
export type ButtonProps = Omit<PressableProps, 'children'> &
|
|
||||||
VariantProps & {
|
|
||||||
children:
|
|
||||||
| ((props: {
|
|
||||||
state: ButtonState
|
|
||||||
type?: ButtonType
|
|
||||||
size?: ButtonSize
|
|
||||||
}) => React.ReactNode)
|
|
||||||
| React.ReactNode
|
|
||||||
| string
|
|
||||||
}
|
|
||||||
export type ButtonTextProps = TextProps & VariantProps
|
|
||||||
|
|
||||||
export function Button({children, style, type, size, ...rest}: ButtonProps) {
|
|
||||||
const {baseStyles, hoverStyles} = React.useMemo(() => {
|
|
||||||
const baseStyles = []
|
|
||||||
const hoverStyles = []
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'primary':
|
|
||||||
baseStyles.push({
|
|
||||||
backgroundColor: tokens.color.blue_500,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 'secondary':
|
|
||||||
baseStyles.push({
|
|
||||||
backgroundColor: tokens.color.gray_200,
|
|
||||||
})
|
|
||||||
hoverStyles.push({
|
|
||||||
backgroundColor: tokens.color.gray_100,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (size) {
|
|
||||||
case 'large':
|
|
||||||
baseStyles.push(
|
|
||||||
atoms.py_md,
|
|
||||||
atoms.px_xl,
|
|
||||||
atoms.rounded_md,
|
|
||||||
atoms.gap_sm,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case 'small':
|
|
||||||
baseStyles.push(
|
|
||||||
atoms.py_sm,
|
|
||||||
atoms.px_md,
|
|
||||||
atoms.rounded_sm,
|
|
||||||
atoms.gap_xs,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
baseStyles,
|
|
||||||
hoverStyles,
|
|
||||||
}
|
|
||||||
}, [type, size])
|
|
||||||
|
|
||||||
const [state, setState] = React.useState({
|
|
||||||
pressed: false,
|
|
||||||
hovered: false,
|
|
||||||
focused: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const onPressIn = React.useCallback(() => {
|
|
||||||
setState(s => ({
|
|
||||||
...s,
|
|
||||||
pressed: true,
|
|
||||||
}))
|
|
||||||
}, [setState])
|
|
||||||
const onPressOut = React.useCallback(() => {
|
|
||||||
setState(s => ({
|
|
||||||
...s,
|
|
||||||
pressed: false,
|
|
||||||
}))
|
|
||||||
}, [setState])
|
|
||||||
const onHoverIn = React.useCallback(() => {
|
|
||||||
setState(s => ({
|
|
||||||
...s,
|
|
||||||
hovered: true,
|
|
||||||
}))
|
|
||||||
}, [setState])
|
|
||||||
const onHoverOut = React.useCallback(() => {
|
|
||||||
setState(s => ({
|
|
||||||
...s,
|
|
||||||
hovered: false,
|
|
||||||
}))
|
|
||||||
}, [setState])
|
|
||||||
const onFocus = React.useCallback(() => {
|
|
||||||
setState(s => ({
|
|
||||||
...s,
|
|
||||||
focused: true,
|
|
||||||
}))
|
|
||||||
}, [setState])
|
|
||||||
const onBlur = React.useCallback(() => {
|
|
||||||
setState(s => ({
|
|
||||||
...s,
|
|
||||||
focused: false,
|
|
||||||
}))
|
|
||||||
}, [setState])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
{...rest}
|
|
||||||
style={state => [
|
|
||||||
atoms.flex_row,
|
|
||||||
atoms.align_center,
|
|
||||||
...baseStyles,
|
|
||||||
...(state.hovered ? hoverStyles : []),
|
|
||||||
typeof style === 'function' ? style(state) : style,
|
|
||||||
]}
|
|
||||||
onPressIn={onPressIn}
|
|
||||||
onPressOut={onPressOut}
|
|
||||||
onHoverIn={onHoverIn}
|
|
||||||
onHoverOut={onHoverOut}
|
|
||||||
onFocus={onFocus}
|
|
||||||
onBlur={onBlur}>
|
|
||||||
{typeof children === 'string' ? (
|
|
||||||
<ButtonText type={type} size={size}>
|
|
||||||
{children}
|
|
||||||
</ButtonText>
|
|
||||||
) : typeof children === 'function' ? (
|
|
||||||
children({state, type, size})
|
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ButtonText({
|
|
||||||
children,
|
|
||||||
style,
|
|
||||||
type,
|
|
||||||
size,
|
|
||||||
...rest
|
|
||||||
}: ButtonTextProps) {
|
|
||||||
const textStyles = React.useMemo(() => {
|
|
||||||
const base = []
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'primary':
|
|
||||||
base.push({color: tokens.color.white})
|
|
||||||
break
|
|
||||||
case 'secondary':
|
|
||||||
base.push({
|
|
||||||
color: tokens.color.gray_700,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (size) {
|
|
||||||
case 'small':
|
|
||||||
base.push(atoms.text_sm, {paddingBottom: 1})
|
|
||||||
break
|
|
||||||
case 'large':
|
|
||||||
base.push(atoms.text_md, {paddingBottom: 1})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
return base
|
|
||||||
}, [type, size])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
{...rest}
|
|
||||||
style={[
|
|
||||||
atoms.flex_1,
|
|
||||||
atoms.font_semibold,
|
|
||||||
atoms.text_center,
|
|
||||||
...textStyles,
|
|
||||||
style,
|
|
||||||
]}>
|
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -22,12 +22,13 @@ import {
|
|||||||
useSetSaveFeedsMutation,
|
useSetSaveFeedsMutation,
|
||||||
DEFAULT_PROD_FEEDS,
|
DEFAULT_PROD_FEEDS,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
import {IS_PROD} from '#/lib/constants'
|
import {FEEDBACK_FORM_URL, IS_PROD} from '#/lib/constants'
|
||||||
|
|
||||||
import {Step1} from './Step1'
|
import {Step1} from './Step1'
|
||||||
import {Step2} from './Step2'
|
import {Step2} from './Step2'
|
||||||
import {Step3} from './Step3'
|
import {Step3} from './Step3'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {TextLink} from '../../util/Link'
|
||||||
|
|
||||||
export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
|
export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
@@ -117,7 +118,7 @@ export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LoggedOutLayout
|
<LoggedOutLayout
|
||||||
leadin={`Step ${uiState.step}`}
|
leadin=""
|
||||||
title={_(msg`Create Account`)}
|
title={_(msg`Create Account`)}
|
||||||
description={_(msg`We're so excited to have you join us!`)}>
|
description={_(msg`We're so excited to have you join us!`)}>
|
||||||
<ScrollView testID="createAccount" style={pal.view}>
|
<ScrollView testID="createAccount" style={pal.view}>
|
||||||
@@ -176,6 +177,27 @@ export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
|
|||||||
</>
|
</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.stepContainer}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
s.flexRow,
|
||||||
|
s.alignCenter,
|
||||||
|
pal.viewLight,
|
||||||
|
{borderRadius: 8, paddingHorizontal: 14, paddingVertical: 12},
|
||||||
|
]}>
|
||||||
|
<Text type="md" style={pal.textLight}>
|
||||||
|
<Trans>Having trouble?</Trans>{' '}
|
||||||
|
</Text>
|
||||||
|
<TextLink
|
||||||
|
type="md"
|
||||||
|
style={pal.link}
|
||||||
|
text={_(msg`Contact support`)}
|
||||||
|
href={FEEDBACK_FORM_URL({email: uiState.email})}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={{height: isTabletOrDesktop ? 50 : 400}} />
|
<View style={{height: isTabletOrDesktop ? 50 : 400}} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</LoggedOutLayout>
|
</LoggedOutLayout>
|
||||||
|
|||||||
+189
-156
@@ -1,25 +1,38 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Keyboard,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
View,
|
||||||
|
} from 'react-native'
|
||||||
|
import {CreateAccountState, CreateAccountDispatch, is18} from './state'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
|
import {DateInput} from 'view/com/util/forms/DateInput'
|
||||||
import {StepHeader} from './StepHeader'
|
import {StepHeader} from './StepHeader'
|
||||||
import {CreateAccountState, CreateAccountDispatch} from './state'
|
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {HelpTip} from '../util/HelpTip'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {TextInput} from '../util/TextInput'
|
import {TextInput} from '../util/TextInput'
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
import {Button} from '../../util/forms/Button'
|
||||||
|
import {Policies} from './Policies'
|
||||||
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {isWeb} from 'platform/detection'
|
||||||
|
import {Trans, msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useModalControls} from '#/state/modals'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
|
|
||||||
import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'lib/constants'
|
function sanitizeDate(date: Date): Date {
|
||||||
import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
|
if (!date || date.toString() === 'Invalid Date') {
|
||||||
|
logger.error(`Create account: handled invalid date for birthDate`, {
|
||||||
|
hasDate: !!date,
|
||||||
|
})
|
||||||
|
return new Date()
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
/** STEP 1: Your hosting provider
|
|
||||||
* @field Bluesky (default)
|
|
||||||
* @field Other (staging, local dev, your own PDS, etc.)
|
|
||||||
*/
|
|
||||||
export function Step1({
|
export function Step1({
|
||||||
uiState,
|
uiState,
|
||||||
uiDispatch,
|
uiDispatch,
|
||||||
@@ -28,136 +41,175 @@ export function Step1({
|
|||||||
uiDispatch: CreateAccountDispatch
|
uiDispatch: CreateAccountDispatch
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const [isDefaultSelected, setIsDefaultSelected] = React.useState(true)
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const {openModal} = useModalControls()
|
||||||
|
|
||||||
const onPressDefault = React.useCallback(() => {
|
const onPressSelectService = React.useCallback(() => {
|
||||||
setIsDefaultSelected(true)
|
openModal({
|
||||||
uiDispatch({type: 'set-service-url', value: PROD_SERVICE})
|
name: 'server-input',
|
||||||
}, [setIsDefaultSelected, uiDispatch])
|
initialService: uiState.serviceUrl,
|
||||||
|
onSelect: (url: string) =>
|
||||||
|
uiDispatch({type: 'set-service-url', value: url}),
|
||||||
|
})
|
||||||
|
Keyboard.dismiss()
|
||||||
|
}, [uiDispatch, uiState.serviceUrl, openModal])
|
||||||
|
|
||||||
const onPressOther = React.useCallback(() => {
|
const onPressWaitlist = React.useCallback(() => {
|
||||||
setIsDefaultSelected(false)
|
openModal({name: 'waitlist'})
|
||||||
uiDispatch({type: 'set-service-url', value: 'https://'})
|
}, [openModal])
|
||||||
}, [setIsDefaultSelected, uiDispatch])
|
|
||||||
|
|
||||||
const onChangeServiceUrl = React.useCallback(
|
const birthDate = React.useMemo(() => {
|
||||||
(v: string) => {
|
return sanitizeDate(uiState.birthDate)
|
||||||
uiDispatch({type: 'set-service-url', value: v})
|
}, [uiState.birthDate])
|
||||||
},
|
|
||||||
[uiDispatch],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<StepHeader step="1" title={_(msg`Your hosting provider`)} />
|
<StepHeader uiState={uiState} title={_(msg`Your account`)}>
|
||||||
<Text style={[pal.text, s.mb10]}>
|
<View>
|
||||||
<Trans>This is the service that keeps you online.</Trans>
|
<Button
|
||||||
</Text>
|
testID="selectServiceButton"
|
||||||
<Option
|
type="default"
|
||||||
testID="blueskyServerBtn"
|
style={{
|
||||||
isSelected={isDefaultSelected}
|
aspectRatio: 1,
|
||||||
label="Bluesky"
|
justifyContent: 'center',
|
||||||
help=" (default)"
|
alignItems: 'center',
|
||||||
onPress={onPressDefault}
|
}}
|
||||||
/>
|
accessibilityLabel={_(msg`Select service`)}
|
||||||
<Option
|
accessibilityHint={_(msg`Sets server for the Bluesky client`)}
|
||||||
testID="otherServerBtn"
|
onPress={onPressSelectService}>
|
||||||
isSelected={!isDefaultSelected}
|
<FontAwesomeIcon icon="server" size={21} />
|
||||||
label="Other"
|
</Button>
|
||||||
onPress={onPressOther}>
|
</View>
|
||||||
<View style={styles.otherForm}>
|
</StepHeader>
|
||||||
<Text nativeID="addressProvider" style={[pal.text, s.mb5]}>
|
|
||||||
<Trans>Enter the address of your provider:</Trans>
|
{!uiState.serviceDescription ? (
|
||||||
</Text>
|
<ActivityIndicator />
|
||||||
<TextInput
|
) : (
|
||||||
testID="customServerInput"
|
<>
|
||||||
icon="globe"
|
{uiState.isInviteCodeRequired && (
|
||||||
placeholder={_(msg`Hosting provider address`)}
|
<View style={s.pb20}>
|
||||||
value={uiState.serviceUrl}
|
<Text type="md-medium" style={[pal.text, s.mb2]}>
|
||||||
editable
|
<Trans>Invite code</Trans>
|
||||||
onChange={onChangeServiceUrl}
|
</Text>
|
||||||
accessibilityHint={_(msg`Input hosting provider address`)}
|
<TextInput
|
||||||
accessibilityLabel={_(msg`Hosting provider address`)}
|
testID="inviteCodeInput"
|
||||||
accessibilityLabelledBy="addressProvider"
|
icon="ticket"
|
||||||
/>
|
placeholder={_(msg`Required for this provider`)}
|
||||||
{LOGIN_INCLUDE_DEV_SERVERS && (
|
value={uiState.inviteCode}
|
||||||
<View style={[s.flexRow, s.mt10]}>
|
editable
|
||||||
<Button
|
onChange={value => uiDispatch({type: 'set-invite-code', value})}
|
||||||
testID="stagingServerBtn"
|
accessibilityLabel={_(msg`Invite code`)}
|
||||||
type="default"
|
accessibilityHint={_(msg`Input invite code to proceed`)}
|
||||||
style={s.mr5}
|
autoCapitalize="none"
|
||||||
label={_(msg`Staging`)}
|
autoComplete="off"
|
||||||
onPress={() => onChangeServiceUrl(STAGING_SERVICE)}
|
autoCorrect={false}
|
||||||
/>
|
autoFocus={true}
|
||||||
<Button
|
|
||||||
testID="localDevServerBtn"
|
|
||||||
type="default"
|
|
||||||
label={_(msg`Dev Server`)}
|
|
||||||
onPress={() => onChangeServiceUrl(LOCAL_DEV_SERVICE)}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
|
||||||
</Option>
|
{!uiState.inviteCode && uiState.isInviteCodeRequired ? (
|
||||||
|
<View style={[s.flexRow, s.alignCenter]}>
|
||||||
|
<Text style={pal.text}>
|
||||||
|
<Trans>Don't have an invite code?</Trans>{' '}
|
||||||
|
</Text>
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
onPress={onPressWaitlist}
|
||||||
|
accessibilityLabel={_(msg`Join the waitlist.`)}
|
||||||
|
accessibilityHint="">
|
||||||
|
<View style={styles.touchable}>
|
||||||
|
<Text style={pal.link}>
|
||||||
|
<Trans>Join the waitlist.</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<View style={s.pb20}>
|
||||||
|
<Text
|
||||||
|
type="md-medium"
|
||||||
|
style={[pal.text, s.mb2]}
|
||||||
|
nativeID="email">
|
||||||
|
<Trans>Email address</Trans>
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
testID="emailInput"
|
||||||
|
icon="envelope"
|
||||||
|
placeholder={_(msg`Enter your email address`)}
|
||||||
|
value={uiState.email}
|
||||||
|
editable
|
||||||
|
onChange={value => uiDispatch({type: 'set-email', value})}
|
||||||
|
accessibilityLabel={_(msg`Email`)}
|
||||||
|
accessibilityHint={_(msg`Input email for Bluesky account`)}
|
||||||
|
accessibilityLabelledBy="email"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect={false}
|
||||||
|
autoFocus={!uiState.isInviteCodeRequired}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={s.pb20}>
|
||||||
|
<Text
|
||||||
|
type="md-medium"
|
||||||
|
style={[pal.text, s.mb2]}
|
||||||
|
nativeID="password">
|
||||||
|
<Trans>Password</Trans>
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
testID="passwordInput"
|
||||||
|
icon="lock"
|
||||||
|
placeholder={_(msg`Choose your password`)}
|
||||||
|
value={uiState.password}
|
||||||
|
editable
|
||||||
|
secureTextEntry
|
||||||
|
onChange={value => uiDispatch({type: 'set-password', value})}
|
||||||
|
accessibilityLabel={_(msg`Password`)}
|
||||||
|
accessibilityHint={_(msg`Set password`)}
|
||||||
|
accessibilityLabelledBy="password"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect={false}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={s.pb20}>
|
||||||
|
<Text
|
||||||
|
type="md-medium"
|
||||||
|
style={[pal.text, s.mb2]}
|
||||||
|
nativeID="birthDate">
|
||||||
|
<Trans>Your birth date</Trans>
|
||||||
|
</Text>
|
||||||
|
<DateInput
|
||||||
|
handleAsUTC
|
||||||
|
testID="birthdayInput"
|
||||||
|
value={birthDate}
|
||||||
|
onChange={value =>
|
||||||
|
uiDispatch({type: 'set-birth-date', value})
|
||||||
|
}
|
||||||
|
buttonType="default-light"
|
||||||
|
buttonStyle={[pal.border, styles.dateInputButton]}
|
||||||
|
buttonLabelType="lg"
|
||||||
|
accessibilityLabel={_(msg`Birthday`)}
|
||||||
|
accessibilityHint={_(msg`Enter your birth date`)}
|
||||||
|
accessibilityLabelledBy="birthDate"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{uiState.serviceDescription && (
|
||||||
|
<Policies
|
||||||
|
serviceDescription={uiState.serviceDescription}
|
||||||
|
needsGuardian={!is18(uiState)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{uiState.error ? (
|
{uiState.error ? (
|
||||||
<ErrorMessage message={uiState.error} style={styles.error} />
|
<ErrorMessage message={uiState.error} style={styles.error} />
|
||||||
) : (
|
) : undefined}
|
||||||
<HelpTip text={_(msg`You can change hosting providers at any time.`)} />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Option({
|
|
||||||
children,
|
|
||||||
isSelected,
|
|
||||||
label,
|
|
||||||
help,
|
|
||||||
onPress,
|
|
||||||
testID,
|
|
||||||
}: React.PropsWithChildren<{
|
|
||||||
isSelected: boolean
|
|
||||||
label: string
|
|
||||||
help?: string
|
|
||||||
onPress: () => void
|
|
||||||
testID?: string
|
|
||||||
}>) {
|
|
||||||
const theme = useTheme()
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const circleFillStyle = React.useMemo(
|
|
||||||
() => ({
|
|
||||||
backgroundColor: theme.palette.primary.background,
|
|
||||||
}),
|
|
||||||
[theme],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[styles.option, pal.border]}>
|
|
||||||
<TouchableWithoutFeedback
|
|
||||||
onPress={onPress}
|
|
||||||
testID={testID}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={label}
|
|
||||||
accessibilityHint={_(msg`Sets hosting provider to ${label}`)}>
|
|
||||||
<View style={styles.optionHeading}>
|
|
||||||
<View style={[styles.circle, pal.border]}>
|
|
||||||
{isSelected ? (
|
|
||||||
<View style={[circleFillStyle, styles.circleFill]} />
|
|
||||||
) : undefined}
|
|
||||||
</View>
|
|
||||||
<Text type="xl" style={pal.text}>
|
|
||||||
{label}
|
|
||||||
{help ? (
|
|
||||||
<Text type="xl" style={pal.textLight}>
|
|
||||||
{help}
|
|
||||||
</Text>
|
|
||||||
) : undefined}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
{isSelected && children}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -165,34 +217,15 @@ function Option({
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
error: {
|
error: {
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
|
marginTop: 10,
|
||||||
},
|
},
|
||||||
|
dateInputButton: {
|
||||||
option: {
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
marginBottom: 10,
|
paddingVertical: 14,
|
||||||
},
|
},
|
||||||
optionHeading: {
|
// @ts-expect-error: Suppressing error due to incomplete `ViewStyle` type definition in react-native-web, missing `cursor` prop as discussed in https://github.com/necolas/react-native-web/issues/832.
|
||||||
flexDirection: 'row',
|
touchable: {
|
||||||
alignItems: 'center',
|
...(isWeb && {cursor: 'pointer'}),
|
||||||
padding: 10,
|
|
||||||
},
|
|
||||||
circle: {
|
|
||||||
width: 26,
|
|
||||||
height: 26,
|
|
||||||
borderRadius: 15,
|
|
||||||
padding: 4,
|
|
||||||
borderWidth: 1,
|
|
||||||
marginRight: 10,
|
|
||||||
},
|
|
||||||
circleFill: {
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
borderRadius: 10,
|
|
||||||
},
|
|
||||||
|
|
||||||
otherForm: {
|
|
||||||
paddingBottom: 10,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
+143
-134
@@ -1,39 +1,28 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
import {
|
||||||
import {CreateAccountState, CreateAccountDispatch, is18} from './state'
|
ActivityIndicator,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
View,
|
||||||
|
} from 'react-native'
|
||||||
|
import {
|
||||||
|
CreateAccountState,
|
||||||
|
CreateAccountDispatch,
|
||||||
|
requestVerificationCode,
|
||||||
|
} from './state'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {DateInput} from 'view/com/util/forms/DateInput'
|
|
||||||
import {StepHeader} from './StepHeader'
|
import {StepHeader} from './StepHeader'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {TextInput} from '../util/TextInput'
|
import {TextInput} from '../util/TextInput'
|
||||||
import {Policies} from './Policies'
|
import {Button} from '../../util/forms/Button'
|
||||||
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {Trans, msg} from '@lingui/macro'
|
import {Trans, msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {logger} from '#/logger'
|
import parsePhoneNumber from 'libphonenumber-js'
|
||||||
|
|
||||||
function sanitizeDate(date: Date): Date {
|
|
||||||
if (!date || date.toString() === 'Invalid Date') {
|
|
||||||
logger.error(`Create account: handled invalid date for birthDate`, {
|
|
||||||
hasDate: !!date,
|
|
||||||
})
|
|
||||||
return new Date()
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
/** STEP 2: Your account
|
|
||||||
* @field Invite code or waitlist
|
|
||||||
* @field Email address
|
|
||||||
* @field Email address
|
|
||||||
* @field Email address
|
|
||||||
* @field Password
|
|
||||||
* @field Birth date
|
|
||||||
* @readonly Terms of service & privacy policy
|
|
||||||
*/
|
|
||||||
export function Step2({
|
export function Step2({
|
||||||
uiState,
|
uiState,
|
||||||
uiDispatch,
|
uiDispatch,
|
||||||
@@ -43,130 +32,155 @@ export function Step2({
|
|||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {openModal} = useModalControls()
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
|
||||||
const onPressWaitlist = React.useCallback(() => {
|
const onPressRequest = React.useCallback(() => {
|
||||||
openModal({name: 'waitlist'})
|
if (
|
||||||
}, [openModal])
|
uiState.verificationPhone.length >= 9 &&
|
||||||
|
parsePhoneNumber(uiState.verificationPhone, 'US')
|
||||||
|
) {
|
||||||
|
requestVerificationCode({uiState, uiDispatch, _})
|
||||||
|
} else {
|
||||||
|
uiDispatch({
|
||||||
|
type: 'set-error',
|
||||||
|
value: _(
|
||||||
|
msg`There's something wrong with this number. Please include your country and/or area code!`,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [uiState, uiDispatch, _])
|
||||||
|
|
||||||
const birthDate = React.useMemo(() => {
|
const onPressRetry = React.useCallback(() => {
|
||||||
return sanitizeDate(uiState.birthDate)
|
uiDispatch({type: 'set-has-requested-verification-code', value: false})
|
||||||
}, [uiState.birthDate])
|
}, [uiDispatch])
|
||||||
|
|
||||||
|
const phoneNumberFormatted = React.useMemo(
|
||||||
|
() =>
|
||||||
|
uiState.hasRequestedVerificationCode
|
||||||
|
? parsePhoneNumber(
|
||||||
|
uiState.verificationPhone,
|
||||||
|
'US',
|
||||||
|
)?.formatInternational()
|
||||||
|
: '',
|
||||||
|
[uiState.hasRequestedVerificationCode, uiState.verificationPhone],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<StepHeader step="2" title={_(msg`Your account`)} />
|
<StepHeader uiState={uiState} title={_(msg`SMS verification`)} />
|
||||||
|
|
||||||
{uiState.isInviteCodeRequired && (
|
{!uiState.hasRequestedVerificationCode ? (
|
||||||
<View style={s.pb20}>
|
<>
|
||||||
<Text type="md-medium" style={[pal.text, s.mb2]}>
|
<View style={s.pb20}>
|
||||||
<Trans>Invite code</Trans>
|
<Text
|
||||||
</Text>
|
type="md-medium"
|
||||||
<TextInput
|
style={[pal.text, s.mb2]}
|
||||||
testID="inviteCodeInput"
|
nativeID="phoneNumber">
|
||||||
icon="ticket"
|
<Trans>Phone number</Trans>
|
||||||
placeholder={_(msg`Required for this provider`)}
|
</Text>
|
||||||
value={uiState.inviteCode}
|
<TextInput
|
||||||
editable
|
testID="phoneInput"
|
||||||
onChange={value => uiDispatch({type: 'set-invite-code', value})}
|
icon="phone"
|
||||||
accessibilityLabel={_(msg`Invite code`)}
|
placeholder={_(msg`Enter your phone number`)}
|
||||||
accessibilityHint={_(msg`Input invite code to proceed`)}
|
value={uiState.verificationPhone}
|
||||||
autoCapitalize="none"
|
editable
|
||||||
autoComplete="off"
|
onChange={value =>
|
||||||
autoCorrect={false}
|
uiDispatch({type: 'set-verification-phone', value})
|
||||||
/>
|
}
|
||||||
</View>
|
accessibilityLabel={_(msg`Email`)}
|
||||||
)}
|
accessibilityHint={_(
|
||||||
|
msg`Input phone number for SMS verification`,
|
||||||
|
)}
|
||||||
|
accessibilityLabelledBy="phoneNumber"
|
||||||
|
keyboardType="phone-pad"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoComplete="tel"
|
||||||
|
autoCorrect={false}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
<Text type="sm" style={[pal.textLight, s.mt5]}>
|
||||||
|
<Trans>
|
||||||
|
Please enter a phone number that can receive SMS text messages.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
{!uiState.inviteCode && uiState.isInviteCodeRequired ? (
|
<View style={isMobile ? {} : {flexDirection: 'row'}}>
|
||||||
<Text style={[s.alignBaseline, pal.text]}>
|
{uiState.isProcessing ? (
|
||||||
<Trans>Don't have an invite code?</Trans>{' '}
|
<ActivityIndicator />
|
||||||
<TouchableWithoutFeedback
|
) : (
|
||||||
onPress={onPressWaitlist}
|
<Button
|
||||||
accessibilityLabel={_(msg`Join the waitlist.`)}
|
testID="requestCodeBtn"
|
||||||
accessibilityHint="">
|
type="primary"
|
||||||
<View style={styles.touchable}>
|
label={_(msg`Request code`)}
|
||||||
<Text style={pal.link}>
|
labelStyle={isMobile ? [s.flex1, s.textCenter, s.f17] : []}
|
||||||
<Trans>Join the waitlist.</Trans>
|
style={
|
||||||
</Text>
|
isMobile ? {paddingVertical: 12, paddingHorizontal: 20} : {}
|
||||||
</View>
|
}
|
||||||
</TouchableWithoutFeedback>
|
onPress={onPressRequest}
|
||||||
</Text>
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<View style={s.pb20}>
|
<View style={s.pb20}>
|
||||||
<Text type="md-medium" style={[pal.text, s.mb2]} nativeID="email">
|
<View
|
||||||
<Trans>Email address</Trans>
|
style={[
|
||||||
</Text>
|
s.flexRow,
|
||||||
|
s.mb5,
|
||||||
|
s.alignCenter,
|
||||||
|
{justifyContent: 'space-between'},
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
type="md-medium"
|
||||||
|
style={pal.text}
|
||||||
|
nativeID="verificationCode">
|
||||||
|
<Trans>Verification code</Trans>{' '}
|
||||||
|
</Text>
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
onPress={onPressRetry}
|
||||||
|
accessibilityLabel={_(msg`Retry.`)}
|
||||||
|
accessibilityHint="">
|
||||||
|
<View style={styles.touchable}>
|
||||||
|
<Text
|
||||||
|
type="md-medium"
|
||||||
|
style={pal.link}
|
||||||
|
nativeID="verificationCode">
|
||||||
|
<Trans>Retry</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</View>
|
||||||
<TextInput
|
<TextInput
|
||||||
testID="emailInput"
|
testID="codeInput"
|
||||||
icon="envelope"
|
icon="hashtag"
|
||||||
placeholder={_(msg`Enter your email address`)}
|
placeholder={_(msg`XXXXXX`)}
|
||||||
value={uiState.email}
|
value={uiState.verificationCode}
|
||||||
editable
|
editable
|
||||||
onChange={value => uiDispatch({type: 'set-email', value})}
|
onChange={value =>
|
||||||
|
uiDispatch({type: 'set-verification-code', value})
|
||||||
|
}
|
||||||
accessibilityLabel={_(msg`Email`)}
|
accessibilityLabel={_(msg`Email`)}
|
||||||
accessibilityHint={_(msg`Input email for Bluesky waitlist`)}
|
accessibilityHint={_(
|
||||||
accessibilityLabelledBy="email"
|
msg`Input the verification code we have texted to you`,
|
||||||
|
)}
|
||||||
|
accessibilityLabelledBy="verificationCode"
|
||||||
|
keyboardType="phone-pad"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoComplete="off"
|
autoComplete="one-time-code"
|
||||||
|
textContentType="oneTimeCode"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
</View>
|
<Text type="sm" style={[pal.textLight, s.mt5]}>
|
||||||
|
<Trans>Please enter the verification code sent to</Trans>{' '}
|
||||||
<View style={s.pb20}>
|
{phoneNumberFormatted}.
|
||||||
<Text
|
|
||||||
type="md-medium"
|
|
||||||
style={[pal.text, s.mb2]}
|
|
||||||
nativeID="password">
|
|
||||||
<Trans>Password</Trans>
|
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput
|
|
||||||
testID="passwordInput"
|
|
||||||
icon="lock"
|
|
||||||
placeholder={_(msg`Choose your password`)}
|
|
||||||
value={uiState.password}
|
|
||||||
editable
|
|
||||||
secureTextEntry
|
|
||||||
onChange={value => uiDispatch({type: 'set-password', value})}
|
|
||||||
accessibilityLabel={_(msg`Password`)}
|
|
||||||
accessibilityHint={_(msg`Set password`)}
|
|
||||||
accessibilityLabelledBy="password"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect={false}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={s.pb20}>
|
|
||||||
<Text
|
|
||||||
type="md-medium"
|
|
||||||
style={[pal.text, s.mb2]}
|
|
||||||
nativeID="birthDate">
|
|
||||||
<Trans>Your birth date</Trans>
|
|
||||||
</Text>
|
|
||||||
<DateInput
|
|
||||||
handleAsUTC
|
|
||||||
testID="birthdayInput"
|
|
||||||
value={birthDate}
|
|
||||||
onChange={value => uiDispatch({type: 'set-birth-date', value})}
|
|
||||||
buttonType="default-light"
|
|
||||||
buttonStyle={[pal.border, styles.dateInputButton]}
|
|
||||||
buttonLabelType="lg"
|
|
||||||
accessibilityLabel={_(msg`Birthday`)}
|
|
||||||
accessibilityHint={_(msg`Enter your birth date`)}
|
|
||||||
accessibilityLabelledBy="birthDate"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{uiState.serviceDescription && (
|
|
||||||
<Policies
|
|
||||||
serviceDescription={uiState.serviceDescription}
|
|
||||||
needsGuardian={!is18(uiState)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{uiState.error ? (
|
{uiState.error ? (
|
||||||
<ErrorMessage message={uiState.error} style={styles.error} />
|
<ErrorMessage message={uiState.error} style={styles.error} />
|
||||||
) : undefined}
|
) : undefined}
|
||||||
@@ -179,11 +193,6 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
},
|
},
|
||||||
dateInputButton: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingVertical: 14,
|
|
||||||
},
|
|
||||||
// @ts-expect-error: Suppressing error due to incomplete `ViewStyle` type definition in react-native-web, missing `cursor` prop as discussed in https://github.com/necolas/react-native-web/issues/832.
|
// @ts-expect-error: Suppressing error due to incomplete `ViewStyle` type definition in react-native-web, missing `cursor` prop as discussed in https://github.com/necolas/react-native-web/issues/832.
|
||||||
touchable: {
|
touchable: {
|
||||||
...(isWeb && {cursor: 'pointer'}),
|
...(isWeb && {cursor: 'pointer'}),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function Step3({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<StepHeader step="3" title={_(msg`Your user handle`)} />
|
<StepHeader uiState={uiState} title={_(msg`Your user handle`)} />
|
||||||
<View style={s.pb10}>
|
<View style={s.pb10}>
|
||||||
<TextInput
|
<TextInput
|
||||||
testID="handleInput"
|
testID="handleInput"
|
||||||
|
|||||||
@@ -3,27 +3,42 @@ import {StyleSheet, View} from 'react-native'
|
|||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {Trans} from '@lingui/macro'
|
import {Trans} from '@lingui/macro'
|
||||||
|
import {CreateAccountState} from './state'
|
||||||
|
|
||||||
export function StepHeader({step, title}: {step: string; title: string}) {
|
export function StepHeader({
|
||||||
|
uiState,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: React.PropsWithChildren<{uiState: CreateAccountState; title: string}>) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
const numSteps = uiState.isPhoneVerificationRequired ? 3 : 2
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text type="lg" style={[pal.textLight]}>
|
<View>
|
||||||
{step === '3' ? (
|
<Text type="lg" style={[pal.textLight]}>
|
||||||
<Trans>Last step!</Trans>
|
{uiState.step === 3 ? (
|
||||||
) : (
|
<Trans>Last step!</Trans>
|
||||||
<Trans>Step {step} of 3</Trans>
|
) : (
|
||||||
)}
|
<Trans>
|
||||||
</Text>
|
Step {uiState.step} of {numSteps}
|
||||||
<Text style={[pal.text]} type="title-xl">
|
</Trans>
|
||||||
{title}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<Text style={[pal.text]} type="title-xl">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{children}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {useReducer} from 'react'
|
|||||||
import {
|
import {
|
||||||
ComAtprotoServerDescribeServer,
|
ComAtprotoServerDescribeServer,
|
||||||
ComAtprotoServerCreateAccount,
|
ComAtprotoServerCreateAccount,
|
||||||
|
BskyAgent,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {I18nContext, useLingui} from '@lingui/react'
|
import {I18nContext, useLingui} from '@lingui/react'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
@@ -13,6 +14,7 @@ import {cleanError} from '#/lib/strings/errors'
|
|||||||
import {DispatchContext as OnboardingDispatchContext} from '#/state/shell/onboarding'
|
import {DispatchContext as OnboardingDispatchContext} from '#/state/shell/onboarding'
|
||||||
import {ApiContext as SessionApiContext} from '#/state/session'
|
import {ApiContext as SessionApiContext} from '#/state/session'
|
||||||
import {DEFAULT_SERVICE} from '#/lib/constants'
|
import {DEFAULT_SERVICE} from '#/lib/constants'
|
||||||
|
import parsePhoneNumber from 'libphonenumber-js'
|
||||||
|
|
||||||
export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||||
const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago
|
const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago
|
||||||
@@ -27,6 +29,9 @@ export type CreateAccountAction =
|
|||||||
| {type: 'set-invite-code'; value: string}
|
| {type: 'set-invite-code'; value: string}
|
||||||
| {type: 'set-email'; value: string}
|
| {type: 'set-email'; value: string}
|
||||||
| {type: 'set-password'; value: string}
|
| {type: 'set-password'; value: string}
|
||||||
|
| {type: 'set-verification-phone'; value: string}
|
||||||
|
| {type: 'set-verification-code'; value: string}
|
||||||
|
| {type: 'set-has-requested-verification-code'; value: boolean}
|
||||||
| {type: 'set-handle'; value: string}
|
| {type: 'set-handle'; value: string}
|
||||||
| {type: 'set-birth-date'; value: Date}
|
| {type: 'set-birth-date'; value: Date}
|
||||||
| {type: 'next'}
|
| {type: 'next'}
|
||||||
@@ -43,6 +48,9 @@ export interface CreateAccountState {
|
|||||||
inviteCode: string
|
inviteCode: string
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password: string
|
||||||
|
verificationPhone: string
|
||||||
|
verificationCode: string
|
||||||
|
hasRequestedVerificationCode: boolean
|
||||||
handle: string
|
handle: string
|
||||||
birthDate: Date
|
birthDate: Date
|
||||||
|
|
||||||
@@ -50,6 +58,7 @@ export interface CreateAccountState {
|
|||||||
canBack: boolean
|
canBack: boolean
|
||||||
canNext: boolean
|
canNext: boolean
|
||||||
isInviteCodeRequired: boolean
|
isInviteCodeRequired: boolean
|
||||||
|
isPhoneVerificationRequired: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateAccountDispatch = (action: CreateAccountAction) => void
|
export type CreateAccountDispatch = (action: CreateAccountAction) => void
|
||||||
@@ -66,15 +75,51 @@ export function useCreateAccount() {
|
|||||||
inviteCode: '',
|
inviteCode: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
verificationPhone: '',
|
||||||
|
verificationCode: '',
|
||||||
|
hasRequestedVerificationCode: false,
|
||||||
handle: '',
|
handle: '',
|
||||||
birthDate: DEFAULT_DATE,
|
birthDate: DEFAULT_DATE,
|
||||||
|
|
||||||
canBack: false,
|
canBack: false,
|
||||||
canNext: false,
|
canNext: false,
|
||||||
isInviteCodeRequired: false,
|
isInviteCodeRequired: false,
|
||||||
|
isPhoneVerificationRequired: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function requestVerificationCode({
|
||||||
|
uiState,
|
||||||
|
uiDispatch,
|
||||||
|
_,
|
||||||
|
}: {
|
||||||
|
uiState: CreateAccountState
|
||||||
|
uiDispatch: CreateAccountDispatch
|
||||||
|
_: I18nContext['_']
|
||||||
|
}) {
|
||||||
|
const phoneNumber = parsePhoneNumber(uiState.verificationPhone, 'US')?.number
|
||||||
|
if (!phoneNumber) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uiDispatch({type: 'set-error', value: ''})
|
||||||
|
uiDispatch({type: 'set-processing', value: true})
|
||||||
|
uiDispatch({type: 'set-verification-phone', value: phoneNumber})
|
||||||
|
try {
|
||||||
|
const agent = new BskyAgent({service: uiState.serviceUrl})
|
||||||
|
await agent.com.atproto.temp.requestPhoneVerification({
|
||||||
|
phoneNumber,
|
||||||
|
})
|
||||||
|
uiDispatch({type: 'set-has-requested-verification-code', value: true})
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to request sms verification code (${e.status} status)`,
|
||||||
|
{error: e},
|
||||||
|
)
|
||||||
|
uiDispatch({type: 'set-error', value: cleanError(e.toString())})
|
||||||
|
}
|
||||||
|
uiDispatch({type: 'set-processing', value: false})
|
||||||
|
}
|
||||||
|
|
||||||
export async function submit({
|
export async function submit({
|
||||||
createAccount,
|
createAccount,
|
||||||
onboardingDispatch,
|
onboardingDispatch,
|
||||||
@@ -89,26 +134,36 @@ export async function submit({
|
|||||||
_: I18nContext['_']
|
_: I18nContext['_']
|
||||||
}) {
|
}) {
|
||||||
if (!uiState.email) {
|
if (!uiState.email) {
|
||||||
uiDispatch({type: 'set-step', value: 2})
|
uiDispatch({type: 'set-step', value: 1})
|
||||||
return uiDispatch({
|
return uiDispatch({
|
||||||
type: 'set-error',
|
type: 'set-error',
|
||||||
value: _(msg`Please enter your email.`),
|
value: _(msg`Please enter your email.`),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!EmailValidator.validate(uiState.email)) {
|
if (!EmailValidator.validate(uiState.email)) {
|
||||||
uiDispatch({type: 'set-step', value: 2})
|
uiDispatch({type: 'set-step', value: 1})
|
||||||
return uiDispatch({
|
return uiDispatch({
|
||||||
type: 'set-error',
|
type: 'set-error',
|
||||||
value: _(msg`Your email appears to be invalid.`),
|
value: _(msg`Your email appears to be invalid.`),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!uiState.password) {
|
if (!uiState.password) {
|
||||||
uiDispatch({type: 'set-step', value: 2})
|
uiDispatch({type: 'set-step', value: 1})
|
||||||
return uiDispatch({
|
return uiDispatch({
|
||||||
type: 'set-error',
|
type: 'set-error',
|
||||||
value: _(msg`Please choose your password.`),
|
value: _(msg`Please choose your password.`),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
uiState.isPhoneVerificationRequired &&
|
||||||
|
(!uiState.verificationPhone || !uiState.verificationCode)
|
||||||
|
) {
|
||||||
|
uiDispatch({type: 'set-step', value: 2})
|
||||||
|
return uiDispatch({
|
||||||
|
type: 'set-error',
|
||||||
|
value: _(msg`Please enter the code you received by SMS.`),
|
||||||
|
})
|
||||||
|
}
|
||||||
if (!uiState.handle) {
|
if (!uiState.handle) {
|
||||||
uiDispatch({type: 'set-step', value: 3})
|
uiDispatch({type: 'set-step', value: 3})
|
||||||
return uiDispatch({
|
return uiDispatch({
|
||||||
@@ -127,6 +182,8 @@ export async function submit({
|
|||||||
handle: createFullHandle(uiState.handle, uiState.userDomain),
|
handle: createFullHandle(uiState.handle, uiState.userDomain),
|
||||||
password: uiState.password,
|
password: uiState.password,
|
||||||
inviteCode: uiState.inviteCode.trim(),
|
inviteCode: uiState.inviteCode.trim(),
|
||||||
|
verificationPhone: uiState.verificationPhone.trim(),
|
||||||
|
verificationCode: uiState.verificationCode.trim(),
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
onboardingDispatch({type: 'skip'}) // undo starting the onboard
|
onboardingDispatch({type: 'skip'}) // undo starting the onboard
|
||||||
@@ -135,6 +192,9 @@ export async function submit({
|
|||||||
errMsg = _(
|
errMsg = _(
|
||||||
msg`Invite code not accepted. Check that you input it correctly and try again.`,
|
msg`Invite code not accepted. Check that you input it correctly and try again.`,
|
||||||
)
|
)
|
||||||
|
uiDispatch({type: 'set-step', value: 1})
|
||||||
|
} else if (e.error === 'InvalidPhoneVerification') {
|
||||||
|
uiDispatch({type: 'set-step', value: 2})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([400, 429].includes(e.status)) {
|
if ([400, 429].includes(e.status)) {
|
||||||
@@ -201,6 +261,19 @@ function createReducer({_}: {_: I18nContext['_']}) {
|
|||||||
case 'set-password': {
|
case 'set-password': {
|
||||||
return compute({...state, password: action.value})
|
return compute({...state, password: action.value})
|
||||||
}
|
}
|
||||||
|
case 'set-verification-phone': {
|
||||||
|
return compute({
|
||||||
|
...state,
|
||||||
|
verificationPhone: action.value,
|
||||||
|
hasRequestedVerificationCode: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'set-verification-code': {
|
||||||
|
return compute({...state, verificationCode: action.value.trim()})
|
||||||
|
}
|
||||||
|
case 'set-has-requested-verification-code': {
|
||||||
|
return compute({...state, hasRequestedVerificationCode: action.value})
|
||||||
|
}
|
||||||
case 'set-handle': {
|
case 'set-handle': {
|
||||||
return compute({...state, handle: action.value})
|
return compute({...state, handle: action.value})
|
||||||
}
|
}
|
||||||
@@ -208,7 +281,7 @@ function createReducer({_}: {_: I18nContext['_']}) {
|
|||||||
return compute({...state, birthDate: action.value})
|
return compute({...state, birthDate: action.value})
|
||||||
}
|
}
|
||||||
case 'next': {
|
case 'next': {
|
||||||
if (state.step === 2) {
|
if (state.step === 1) {
|
||||||
if (!is13(state)) {
|
if (!is13(state)) {
|
||||||
return compute({
|
return compute({
|
||||||
...state,
|
...state,
|
||||||
@@ -218,10 +291,18 @@ function createReducer({_}: {_: I18nContext['_']}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return compute({...state, error: '', step: state.step + 1})
|
let increment = 1
|
||||||
|
if (state.step === 1 && !state.isPhoneVerificationRequired) {
|
||||||
|
increment = 2
|
||||||
|
}
|
||||||
|
return compute({...state, error: '', step: state.step + increment})
|
||||||
}
|
}
|
||||||
case 'back': {
|
case 'back': {
|
||||||
return compute({...state, error: '', step: state.step - 1})
|
let decrement = 1
|
||||||
|
if (state.step === 3 && !state.isPhoneVerificationRequired) {
|
||||||
|
decrement = 2
|
||||||
|
}
|
||||||
|
return compute({...state, error: '', step: state.step - decrement})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,12 +311,16 @@ function createReducer({_}: {_: I18nContext['_']}) {
|
|||||||
function compute(state: CreateAccountState): CreateAccountState {
|
function compute(state: CreateAccountState): CreateAccountState {
|
||||||
let canNext = true
|
let canNext = true
|
||||||
if (state.step === 1) {
|
if (state.step === 1) {
|
||||||
canNext = !!state.serviceDescription
|
|
||||||
} else if (state.step === 2) {
|
|
||||||
canNext =
|
canNext =
|
||||||
|
!!state.serviceDescription &&
|
||||||
(!state.isInviteCodeRequired || !!state.inviteCode) &&
|
(!state.isInviteCodeRequired || !!state.inviteCode) &&
|
||||||
!!state.email &&
|
!!state.email &&
|
||||||
!!state.password
|
!!state.password
|
||||||
|
} else if (state.step === 2) {
|
||||||
|
canNext =
|
||||||
|
!state.isPhoneVerificationRequired ||
|
||||||
|
(!!state.verificationPhone &&
|
||||||
|
isValidVerificationCode(state.verificationCode))
|
||||||
} else if (state.step === 3) {
|
} else if (state.step === 3) {
|
||||||
canNext = !!state.handle
|
canNext = !!state.handle
|
||||||
}
|
}
|
||||||
@@ -244,5 +329,11 @@ function compute(state: CreateAccountState): CreateAccountState {
|
|||||||
canBack: state.step > 1,
|
canBack: state.step > 1,
|
||||||
canNext,
|
canNext,
|
||||||
isInviteCodeRequired: !!state.serviceDescription?.inviteCodeRequired,
|
isInviteCodeRequired: !!state.serviceDescription?.inviteCodeRequired,
|
||||||
|
isPhoneVerificationRequired:
|
||||||
|
!!state.serviceDescription?.phoneVerificationRequired,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidVerificationCode(str: string): boolean {
|
||||||
|
return /[0-9]{6}/.test(str)
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {data: currentProfile} = useProfileQuery({did: currentAccount!.did})
|
const {data: currentProfile} = useProfileQuery({did: currentAccount!.did})
|
||||||
const {activeModals} = useModals()
|
const {isModalActive, activeModals} = useModals()
|
||||||
const {openModal, closeModal} = useModalControls()
|
const {openModal, closeModal} = useModalControls()
|
||||||
const {closeComposer} = useComposerControls()
|
const {closeComposer} = useComposerControls()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
@@ -176,11 +176,11 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
[onPressCancel],
|
[onPressCancel],
|
||||||
)
|
)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isWeb) {
|
if (isWeb && !isModalActive) {
|
||||||
window.addEventListener('keydown', onEscape)
|
window.addEventListener('keydown', onEscape)
|
||||||
return () => window.removeEventListener('keydown', onEscape)
|
return () => window.removeEventListener('keydown', onEscape)
|
||||||
}
|
}
|
||||||
}, [onEscape])
|
}, [onEscape, isModalActive])
|
||||||
|
|
||||||
const onPressAddLinkCard = useCallback(
|
const onPressAddLinkCard = useCallback(
|
||||||
(uri: string) => {
|
(uri: string) => {
|
||||||
|
|||||||
@@ -94,15 +94,23 @@ export const ListCard = ({
|
|||||||
</Trans>
|
</Trans>
|
||||||
))}
|
))}
|
||||||
</Text>
|
</Text>
|
||||||
{!!list.viewer?.muted && (
|
<View style={s.flexRow}>
|
||||||
<View style={s.flexRow}>
|
{list.viewer?.muted ? (
|
||||||
<View style={[s.mt5, pal.btn, styles.pill]}>
|
<View style={[s.mt5, pal.btn, styles.pill]}>
|
||||||
<Text type="xs" style={pal.text}>
|
<Text type="xs" style={pal.text}>
|
||||||
<Trans>Subscribed</Trans>
|
<Trans>Muted</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
) : null}
|
||||||
)}
|
|
||||||
|
{list.viewer?.blocked ? (
|
||||||
|
<View style={[s.mt5, pal.btn, styles.pill]}>
|
||||||
|
<Text type="xs" style={pal.text}>
|
||||||
|
<Trans>Blocked</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{renderButton ? (
|
{renderButton ? (
|
||||||
<View style={styles.layoutButton}>{renderButton()}</View>
|
<View style={styles.layoutButton}>{renderButton()}</View>
|
||||||
|
|||||||
@@ -182,19 +182,17 @@ export function Component({
|
|||||||
]}
|
]}
|
||||||
testID="createOrEditListModal">
|
testID="createOrEditListModal">
|
||||||
<Text style={[styles.title, pal.text]}>
|
<Text style={[styles.title, pal.text]}>
|
||||||
<Trans>
|
{isCurateList ? (
|
||||||
{isCurateList ? (
|
list ? (
|
||||||
list ? (
|
<Trans>Edit User List</Trans>
|
||||||
<Trans>Edit User List</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans>New User List</Trans>
|
|
||||||
)
|
|
||||||
) : list ? (
|
|
||||||
<Trans>Edit Moderation List</Trans>
|
|
||||||
) : (
|
) : (
|
||||||
<Trans>New Moderation List</Trans>
|
<Trans>New User List</Trans>
|
||||||
)}
|
)
|
||||||
</Trans>
|
) : list ? (
|
||||||
|
<Trans>Edit Moderation List</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>New Moderation List</Trans>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
{error !== '' && (
|
{error !== '' && (
|
||||||
<View style={styles.errorContainer}>
|
<View style={styles.errorContainer}>
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export function Component({}: {}) {
|
|||||||
{/* TODO: Update this label to be more concise */}
|
{/* TODO: Update this label to be more concise */}
|
||||||
<Text
|
<Text
|
||||||
type="lg"
|
type="lg"
|
||||||
style={styles.description}
|
style={[pal.text, styles.description]}
|
||||||
nativeID="confirmationCode">
|
nativeID="confirmationCode">
|
||||||
<Trans>
|
<Trans>
|
||||||
Check your inbox for an email with the confirmation code to
|
Check your inbox for an email with the confirmation code to
|
||||||
@@ -180,7 +180,10 @@ export function Component({}: {}) {
|
|||||||
msg`Input confirmation code for account deletion`,
|
msg`Input confirmation code for account deletion`,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Text type="lg" style={styles.description} nativeID="password">
|
<Text
|
||||||
|
type="lg"
|
||||||
|
style={[pal.text, styles.description]}
|
||||||
|
nativeID="password">
|
||||||
<Trans>Please enter your password as well:</Trans>
|
<Trans>Please enter your password as well:</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onPressMask = () => {
|
const onPressMask = () => {
|
||||||
if (modal.name === 'crop-image' || modal.name === 'edit-image') {
|
if (
|
||||||
|
modal.name === 'crop-image' ||
|
||||||
|
modal.name === 'edit-image' ||
|
||||||
|
modal.name === 'alt-text-image'
|
||||||
|
) {
|
||||||
return // dont close on mask presses during crop
|
return // dont close on mask presses during crop
|
||||||
}
|
}
|
||||||
closeModal()
|
closeModal()
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ import {useNavigation} from '@react-navigation/native'
|
|||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
|
|
||||||
|
import {IS_DEV} from '#/env'
|
||||||
|
import {atoms} from '#/alf'
|
||||||
|
import {Link as Link2} from '#/components/Link'
|
||||||
|
import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
|
||||||
|
|
||||||
export function FeedsTabBar(
|
export function FeedsTabBar(
|
||||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||||
) {
|
) {
|
||||||
@@ -68,7 +73,7 @@ export function FeedsTabBar(
|
|||||||
headerHeight.value = e.nativeEvent.layout.height
|
headerHeight.value = e.nativeEvent.layout.height
|
||||||
}}>
|
}}>
|
||||||
<View style={[pal.view, styles.topBar]}>
|
<View style={[pal.view, styles.topBar]}>
|
||||||
<View style={[pal.view]}>
|
<View style={[pal.view, {width: 100}]}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID="viewHeaderDrawerBtn"
|
testID="viewHeaderDrawerBtn"
|
||||||
onPress={onPressAvi}
|
onPress={onPressAvi}
|
||||||
@@ -88,7 +93,21 @@ export function FeedsTabBar(
|
|||||||
<View>
|
<View>
|
||||||
<Logo width={30} />
|
<Logo width={30} />
|
||||||
</View>
|
</View>
|
||||||
<View style={[pal.view, {width: 18}]}>
|
<View
|
||||||
|
style={[
|
||||||
|
atoms.flex_row,
|
||||||
|
atoms.justify_end,
|
||||||
|
atoms.align_center,
|
||||||
|
atoms.gap_md,
|
||||||
|
pal.view,
|
||||||
|
{width: 100},
|
||||||
|
]}>
|
||||||
|
{IS_DEV && (
|
||||||
|
<Link2 to="/sys/debug">
|
||||||
|
<ColorPalette size="md" />
|
||||||
|
</Link2>
|
||||||
|
)}
|
||||||
|
|
||||||
{hasSession && (
|
{hasSession && (
|
||||||
<Link
|
<Link
|
||||||
testID="viewHeaderHomeFeedPrefsBtn"
|
testID="viewHeaderHomeFeedPrefsBtn"
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {Trans} from '@lingui/macro'
|
||||||
|
import {Text} from '../util/text/Text'
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {TextLink} from '../util/Link'
|
||||||
|
import {InfoCircleIcon} from '#/lib/icons'
|
||||||
|
|
||||||
|
export function DiscoverFallbackHeader() {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
},
|
||||||
|
pal.border,
|
||||||
|
pal.viewLight,
|
||||||
|
]}>
|
||||||
|
<View style={{width: 68, paddingLeft: 12}}>
|
||||||
|
<InfoCircleIcon size={36} style={pal.textLight} strokeWidth={1.5} />
|
||||||
|
</View>
|
||||||
|
<View style={{flex: 1}}>
|
||||||
|
<Text type="md" style={pal.text}>
|
||||||
|
<Trans>
|
||||||
|
We ran out of posts from your follows. Here's the latest from{' '}
|
||||||
|
<TextLink
|
||||||
|
type="md-medium"
|
||||||
|
href="/profile/bsky.app/feed/whats-hot"
|
||||||
|
text="Discover"
|
||||||
|
style={pal.link}
|
||||||
|
/>
|
||||||
|
.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ import {useSession} from '#/state/session'
|
|||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
|
||||||
|
import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home'
|
||||||
|
|
||||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
@@ -265,6 +267,12 @@ let Feed = ({
|
|||||||
)
|
)
|
||||||
} else if (item === LOADING_ITEM) {
|
} else if (item === LOADING_ITEM) {
|
||||||
return <PostFeedLoadingPlaceholder />
|
return <PostFeedLoadingPlaceholder />
|
||||||
|
} else if (item.rootUri === FALLBACK_MARKER_POST.post.uri) {
|
||||||
|
// HACK
|
||||||
|
// tell the user we fell back to discover
|
||||||
|
// see home.ts (feed api) for more info
|
||||||
|
// -prf
|
||||||
|
return <DiscoverFallbackHeader />
|
||||||
}
|
}
|
||||||
return <FeedSlice slice={item} />
|
return <FeedSlice slice={item} />
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -306,6 +306,8 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const EXEMPT_PATHS = ['/robots.txt', '/security.txt', '/.well-known/']
|
||||||
|
|
||||||
// NOTE
|
// NOTE
|
||||||
// we can't use the onPress given by useLinkProps because it will
|
// we can't use the onPress given by useLinkProps because it will
|
||||||
// match most paths to the HomeTab routes while we actually want to
|
// match most paths to the HomeTab routes while we actually want to
|
||||||
@@ -350,7 +352,12 @@ function onPressInner(
|
|||||||
|
|
||||||
if (shouldHandle) {
|
if (shouldHandle) {
|
||||||
href = convertBskyAppUrlIfNeeded(href)
|
href = convertBskyAppUrlIfNeeded(href)
|
||||||
if (newTab || href.startsWith('http') || href.startsWith('mailto')) {
|
if (
|
||||||
|
newTab ||
|
||||||
|
href.startsWith('http') ||
|
||||||
|
href.startsWith('mailto') ||
|
||||||
|
EXEMPT_PATHS.some(path => href.startsWith(path))
|
||||||
|
) {
|
||||||
openLink(href)
|
openLink(href)
|
||||||
} else {
|
} else {
|
||||||
closeModal() // close any active modals
|
closeModal() // close any active modals
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import {Link} from '../Link'
|
|||||||
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
||||||
import {useLightboxControls, ImagesLightbox} from '#/state/lightbox'
|
import {useLightboxControls, ImagesLightbox} from '#/state/lightbox'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
||||||
import {MaybeQuoteEmbed} from './QuoteEmbed'
|
import {MaybeQuoteEmbed} from './QuoteEmbed'
|
||||||
import {AutoSizedImage} from '../images/AutoSizedImage'
|
import {AutoSizedImage} from '../images/AutoSizedImage'
|
||||||
@@ -51,7 +50,6 @@ export function PostEmbeds({
|
|||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {openLightbox} = useLightboxControls()
|
const {openLightbox} = useLightboxControls()
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
|
|
||||||
// quote post with media
|
// quote post with media
|
||||||
// =
|
// =
|
||||||
@@ -129,10 +127,7 @@ export function PostEmbeds({
|
|||||||
dimensionsHint={aspectRatio}
|
dimensionsHint={aspectRatio}
|
||||||
onPress={() => _openLightbox(0)}
|
onPress={() => _openLightbox(0)}
|
||||||
onPressIn={() => onPressIn(0)}
|
onPressIn={() => onPressIn(0)}
|
||||||
style={[
|
style={[styles.singleImage]}>
|
||||||
styles.singleImage,
|
|
||||||
isMobile && styles.singleImageMobile,
|
|
||||||
]}>
|
|
||||||
{alt === '' ? null : (
|
{alt === '' ? null : (
|
||||||
<View style={styles.altContainer}>
|
<View style={styles.altContainer}>
|
||||||
<Text style={styles.alt} accessible={false}>
|
<Text style={styles.alt} accessible={false}>
|
||||||
@@ -151,11 +146,7 @@ export function PostEmbeds({
|
|||||||
images={embed.images}
|
images={embed.images}
|
||||||
onPress={_openLightbox}
|
onPress={_openLightbox}
|
||||||
onPressIn={onPressIn}
|
onPressIn={onPressIn}
|
||||||
style={
|
style={embed.images.length === 1 ? [styles.singleImage] : undefined}
|
||||||
embed.images.length === 1
|
|
||||||
? [styles.singleImage, isMobile && styles.singleImageMobile]
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -188,10 +179,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
singleImage: {
|
singleImage: {
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
maxHeight: 1000,
|
|
||||||
},
|
|
||||||
singleImageMobile: {
|
|
||||||
maxHeight: 500,
|
|
||||||
},
|
},
|
||||||
extOuter: {
|
extOuter: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {StyleSheet, TextProps} from 'react-native'
|
||||||
import Svg, {
|
import Svg, {
|
||||||
Path,
|
Path,
|
||||||
Defs,
|
Defs,
|
||||||
@@ -14,12 +15,14 @@ const ratio = 57 / 64
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
fill?: PathProps['fill']
|
fill?: PathProps['fill']
|
||||||
} & SvgProps
|
style?: TextProps['style']
|
||||||
|
} & Omit<SvgProps, 'style'>
|
||||||
|
|
||||||
export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
|
export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
|
||||||
const {fill, ...rest} = props
|
const {fill, ...rest} = props
|
||||||
const gradient = fill === 'sky'
|
const gradient = fill === 'sky'
|
||||||
const _fill = gradient ? 'url(#sky)' : fill || colors.blue3
|
const styles = StyleSheet.flatten(props.style)
|
||||||
|
const _fill = gradient ? 'url(#sky)' : fill || styles?.color || colors.blue3
|
||||||
// @ts-ignore it's fiiiiine
|
// @ts-ignore it's fiiiiine
|
||||||
const size = parseInt(rest.width || 32)
|
const size = parseInt(rest.width || 32)
|
||||||
return (
|
return (
|
||||||
@@ -29,7 +32,7 @@ export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
viewBox="0 0 64 57"
|
viewBox="0 0 64 57"
|
||||||
{...rest}
|
{...rest}
|
||||||
style={{width: size, height: size * ratio}}>
|
style={[{width: size, height: size * ratio}, styles]}>
|
||||||
{gradient && (
|
{gradient && (
|
||||||
<Defs>
|
<Defs>
|
||||||
<LinearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
<LinearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import {faGear} from '@fortawesome/free-solid-svg-icons/faGear'
|
|||||||
import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe'
|
import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe'
|
||||||
import {faHand} from '@fortawesome/free-solid-svg-icons/faHand'
|
import {faHand} from '@fortawesome/free-solid-svg-icons/faHand'
|
||||||
import {faHand as farHand} from '@fortawesome/free-regular-svg-icons/faHand'
|
import {faHand as farHand} from '@fortawesome/free-regular-svg-icons/faHand'
|
||||||
|
import {faHashtag} from '@fortawesome/free-solid-svg-icons/faHashtag'
|
||||||
import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart'
|
import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart'
|
||||||
import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart'
|
import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart'
|
||||||
import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse'
|
import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse'
|
||||||
@@ -71,6 +72,7 @@ import {faPaste} from '@fortawesome/free-regular-svg-icons/faPaste'
|
|||||||
import {faPen} from '@fortawesome/free-solid-svg-icons/faPen'
|
import {faPen} from '@fortawesome/free-solid-svg-icons/faPen'
|
||||||
import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
|
import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
|
||||||
import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare'
|
import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare'
|
||||||
|
import {faPhone} from '@fortawesome/free-solid-svg-icons/faPhone'
|
||||||
import {faPlay} from '@fortawesome/free-solid-svg-icons/faPlay'
|
import {faPlay} from '@fortawesome/free-solid-svg-icons/faPlay'
|
||||||
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
|
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
|
||||||
import {faQuoteLeft} from '@fortawesome/free-solid-svg-icons/faQuoteLeft'
|
import {faQuoteLeft} from '@fortawesome/free-solid-svg-icons/faQuoteLeft'
|
||||||
@@ -78,6 +80,7 @@ import {faReply} from '@fortawesome/free-solid-svg-icons/faReply'
|
|||||||
import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
|
import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
|
||||||
import {faRss} from '@fortawesome/free-solid-svg-icons/faRss'
|
import {faRss} from '@fortawesome/free-solid-svg-icons/faRss'
|
||||||
import {faSatelliteDish} from '@fortawesome/free-solid-svg-icons/faSatelliteDish'
|
import {faSatelliteDish} from '@fortawesome/free-solid-svg-icons/faSatelliteDish'
|
||||||
|
import {faServer} from '@fortawesome/free-solid-svg-icons/faServer'
|
||||||
import {faShare} from '@fortawesome/free-solid-svg-icons/faShare'
|
import {faShare} from '@fortawesome/free-solid-svg-icons/faShare'
|
||||||
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
||||||
import {faShield} from '@fortawesome/free-solid-svg-icons/faShield'
|
import {faShield} from '@fortawesome/free-solid-svg-icons/faShield'
|
||||||
@@ -153,6 +156,7 @@ library.add(
|
|||||||
faGlobe,
|
faGlobe,
|
||||||
faHand,
|
faHand,
|
||||||
farHand,
|
farHand,
|
||||||
|
faHashtag,
|
||||||
faHeart,
|
faHeart,
|
||||||
fasHeart,
|
fasHeart,
|
||||||
faHouse,
|
faHouse,
|
||||||
@@ -172,6 +176,7 @@ library.add(
|
|||||||
faPen,
|
faPen,
|
||||||
faPenNib,
|
faPenNib,
|
||||||
faPenToSquare,
|
faPenToSquare,
|
||||||
|
faPhone,
|
||||||
faPlay,
|
faPlay,
|
||||||
faPlus,
|
faPlus,
|
||||||
faQuoteLeft,
|
faQuoteLeft,
|
||||||
@@ -179,6 +184,7 @@ library.add(
|
|||||||
faRetweet,
|
faRetweet,
|
||||||
faRss,
|
faRss,
|
||||||
faSatelliteDish,
|
faSatelliteDish,
|
||||||
|
faServer,
|
||||||
faShare,
|
faShare,
|
||||||
faShareFromSquare,
|
faShareFromSquare,
|
||||||
faShield,
|
faShield,
|
||||||
|
|||||||
@@ -1,541 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {View} from 'react-native'
|
|
||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
|
|
||||||
import {useSetColorMode} from '#/state/shell'
|
|
||||||
import * as tokens from '#/alf/tokens'
|
|
||||||
import {atoms as a, useTheme, useBreakpoints, ThemeProvider as Alf} from '#/alf'
|
|
||||||
import {Button, ButtonText} from '#/view/com/Button'
|
|
||||||
import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
|
|
||||||
|
|
||||||
function ThemeSelector() {
|
|
||||||
const setColorMode = useSetColorMode()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<Button
|
|
||||||
type="secondary"
|
|
||||||
size="small"
|
|
||||||
onPress={() => setColorMode('system')}>
|
|
||||||
System
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="secondary"
|
|
||||||
size="small"
|
|
||||||
onPress={() => setColorMode('light')}>
|
|
||||||
Light
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="secondary"
|
|
||||||
size="small"
|
|
||||||
onPress={() => setColorMode('dark')}>
|
|
||||||
Dark
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function BreakpointDebugger() {
|
|
||||||
const t = useTheme()
|
|
||||||
const breakpoints = useBreakpoints()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<H3 style={[a.pb_md]}>Breakpoint Debugger</H3>
|
|
||||||
<Text style={[a.pb_md]}>
|
|
||||||
Current breakpoint: {!breakpoints.gtMobile && <Text>mobile</Text>}
|
|
||||||
{breakpoints.gtMobile && !breakpoints.gtTablet && <Text>tablet</Text>}
|
|
||||||
{breakpoints.gtTablet && <Text>desktop</Text>}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[a.p_md, t.atoms.bg_contrast_100, {fontFamily: 'monospace'}]}>
|
|
||||||
{JSON.stringify(breakpoints, null, 2)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ThemedSection() {
|
|
||||||
const t = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[t.atoms.bg, a.gap_md, a.p_xl]}>
|
|
||||||
<H3 style={[a.font_bold]}>theme.atoms.text</H3>
|
|
||||||
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
|
|
||||||
<H3 style={[a.font_bold, t.atoms.text_contrast_700]}>
|
|
||||||
theme.atoms.text_contrast_700
|
|
||||||
</H3>
|
|
||||||
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
|
|
||||||
<H3 style={[a.font_bold, t.atoms.text_contrast_500]}>
|
|
||||||
theme.atoms.text_contrast_500
|
|
||||||
</H3>
|
|
||||||
<View style={[a.flex_1, t.atoms.border_contrast_500, a.border_t]} />
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.bg,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
{height: 60},
|
|
||||||
]}>
|
|
||||||
<Text>theme.bg</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.bg_contrast_100,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
{height: 60},
|
|
||||||
]}>
|
|
||||||
<Text>theme.bg_contrast_100</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.bg_contrast_200,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
{height: 60},
|
|
||||||
]}>
|
|
||||||
<Text>theme.bg_contrast_200</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.bg_contrast_300,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
{height: 60},
|
|
||||||
]}>
|
|
||||||
<Text>theme.bg_contrast_300</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.bg_positive,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
{height: 60},
|
|
||||||
]}>
|
|
||||||
<Text>theme.bg_positive</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.bg_negative,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
{height: 60},
|
|
||||||
]}>
|
|
||||||
<Text>theme.bg_negative</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DebugScreen() {
|
|
||||||
const t = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView>
|
|
||||||
<CenteredView style={[t.atoms.bg]}>
|
|
||||||
<View style={[a.p_xl, a.gap_xxl, {paddingBottom: 200}]}>
|
|
||||||
<ThemeSelector />
|
|
||||||
|
|
||||||
<Alf theme="light">
|
|
||||||
<ThemedSection />
|
|
||||||
</Alf>
|
|
||||||
<Alf theme="dark">
|
|
||||||
<ThemedSection />
|
|
||||||
</Alf>
|
|
||||||
|
|
||||||
<H1>Heading 1</H1>
|
|
||||||
<H2>Heading 2</H2>
|
|
||||||
<H3>Heading 3</H3>
|
|
||||||
<H4>Heading 4</H4>
|
|
||||||
<H5>Heading 5</H5>
|
|
||||||
<H6>Heading 6</H6>
|
|
||||||
|
|
||||||
<Text style={[a.text_xxl]}>atoms.text_xxl</Text>
|
|
||||||
<Text style={[a.text_xl]}>atoms.text_xl</Text>
|
|
||||||
<Text style={[a.text_lg]}>atoms.text_lg</Text>
|
|
||||||
<Text style={[a.text_md]}>atoms.text_md</Text>
|
|
||||||
<Text style={[a.text_sm]}>atoms.text_sm</Text>
|
|
||||||
<Text style={[a.text_xs]}>atoms.text_xs</Text>
|
|
||||||
<Text style={[a.text_xxs]}>atoms.text_xxs</Text>
|
|
||||||
|
|
||||||
<View style={[a.gap_md, a.align_start]}>
|
|
||||||
<Button>
|
|
||||||
{({state}) => (
|
|
||||||
<View style={[a.p_md, a.rounded_full, t.atoms.bg_contrast_300]}>
|
|
||||||
<Text>Unstyled button, state: {JSON.stringify(state)}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button type="primary" size="small">
|
|
||||||
Button
|
|
||||||
</Button>
|
|
||||||
<Button type="secondary" size="small">
|
|
||||||
Button
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button type="primary" size="large">
|
|
||||||
Button
|
|
||||||
</Button>
|
|
||||||
<Button type="secondary" size="large">
|
|
||||||
Button
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button type="secondary" size="small">
|
|
||||||
{({type, size}) => (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon icon={['fas', 'plus']} size={12} />
|
|
||||||
<ButtonText type={type} size={size}>
|
|
||||||
With an icon
|
|
||||||
</ButtonText>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<Button type="primary" size="large">
|
|
||||||
{({state: _state, ...rest}) => (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon icon={['fas', 'plus']} />
|
|
||||||
<ButtonText {...rest}>With an icon</ButtonText>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.gap_md]}>
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_0},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_100},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_200},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_300},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_400},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_500},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_600},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_700},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_800},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_900},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.gray_1000},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_0},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_100},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_200},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_300},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_400},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_500},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_600},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_700},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_800},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_900},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.blue_1000},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_0},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_100},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_200},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_300},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_400},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_500},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_600},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_700},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_800},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_900},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.green_1000},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_0},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_100},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_200},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_300},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_400},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_500},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_600},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_700},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_800},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_900},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
{height: 60, backgroundColor: tokens.color.red_1000},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View>
|
|
||||||
<H3 style={[a.pb_md, a.font_bold]}>Spacing</H3>
|
|
||||||
|
|
||||||
<View style={[a.gap_md]}>
|
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
|
||||||
<Text style={{width: 80}}>xxs (2px)</Text>
|
|
||||||
<View style={[a.flex_1, a.pt_xxs, t.atoms.bg_contrast_300]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
|
||||||
<Text style={{width: 80}}>xs (4px)</Text>
|
|
||||||
<View style={[a.flex_1, a.pt_xs, t.atoms.bg_contrast_300]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
|
||||||
<Text style={{width: 80}}>sm (8px)</Text>
|
|
||||||
<View style={[a.flex_1, a.pt_sm, t.atoms.bg_contrast_300]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
|
||||||
<Text style={{width: 80}}>md (12px)</Text>
|
|
||||||
<View style={[a.flex_1, a.pt_md, t.atoms.bg_contrast_300]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
|
||||||
<Text style={{width: 80}}>lg (18px)</Text>
|
|
||||||
<View style={[a.flex_1, a.pt_lg, t.atoms.bg_contrast_300]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
|
||||||
<Text style={{width: 80}}>xl (24px)</Text>
|
|
||||||
<View style={[a.flex_1, a.pt_xl, t.atoms.bg_contrast_300]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
|
||||||
<Text style={{width: 80}}>xxl (32px)</Text>
|
|
||||||
<View style={[a.flex_1, a.pt_xxl, t.atoms.bg_contrast_300]} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<BreakpointDebugger />
|
|
||||||
</View>
|
|
||||||
</CenteredView>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ import {useSession} from '#/state/session'
|
|||||||
import {loadString, saveString} from '#/lib/storage'
|
import {loadString, saveString} from '#/lib/storage'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
||||||
export function HomeScreen(props: Props) {
|
export function HomeScreen(props: Props) {
|
||||||
@@ -112,7 +111,7 @@ function HomeScreenReady({
|
|||||||
mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled),
|
mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled),
|
||||||
mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled
|
mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled
|
||||||
? preferences.feeds.saved
|
? preferences.feeds.saved
|
||||||
: [PROD_DEFAULT_FEED('whats-hot')],
|
: [],
|
||||||
}
|
}
|
||||||
}, [preferences])
|
}, [preferences])
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,13 @@ import {useSetDrawerOpen} from '#/state/shell'
|
|||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {MagnifyingGlassIcon} from '#/lib/icons'
|
import {MagnifyingGlassIcon} from '#/lib/icons'
|
||||||
import {useModerationOpts} from '#/state/queries/preferences'
|
import {useModerationOpts} from '#/state/queries/preferences'
|
||||||
import {SearchResultCard} from '#/view/shell/desktop/Search'
|
import {
|
||||||
|
MATCH_HANDLE,
|
||||||
|
SearchLinkCard,
|
||||||
|
SearchProfileCard,
|
||||||
|
} from '#/view/shell/desktop/Search'
|
||||||
import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
|
import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {listenSoftReset} from '#/state/events'
|
import {listenSoftReset} from '#/state/events'
|
||||||
import {s} from '#/lib/styles'
|
import {s} from '#/lib/styles'
|
||||||
|
|
||||||
@@ -83,9 +87,7 @@ function EmptyState({message, error}: {message: string; error?: string}) {
|
|||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
|
<View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
|
||||||
<Text style={[pal.text]}>
|
<Text style={[pal.text]}>{message}</Text>
|
||||||
<Trans>{message}</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<>
|
<>
|
||||||
@@ -511,6 +513,11 @@ export function SearchScreen(
|
|||||||
onPressCancelSearch()
|
onPressCancelSearch()
|
||||||
}, [onPressCancelSearch])
|
}, [onPressCancelSearch])
|
||||||
|
|
||||||
|
const queryMaybeHandle = React.useMemo(() => {
|
||||||
|
const match = MATCH_HANDLE.exec(query)
|
||||||
|
return match && match[1]
|
||||||
|
}, [query])
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
React.useCallback(() => {
|
||||||
setMinimalShellMode(false)
|
setMinimalShellMode(false)
|
||||||
@@ -617,18 +624,31 @@ export function SearchScreen(
|
|||||||
dataSet={{stableGutters: '1'}}
|
dataSet={{stableGutters: '1'}}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
keyboardDismissMode="on-drag">
|
keyboardDismissMode="on-drag">
|
||||||
{searchResults.length ? (
|
<SearchLinkCard
|
||||||
searchResults.map((item, i) => (
|
label={_(msg`Search for "${query}"`)}
|
||||||
<SearchResultCard
|
onPress={isNative ? onSubmit : undefined}
|
||||||
key={item.did}
|
to={
|
||||||
profile={item}
|
isNative
|
||||||
moderation={moderateProfile(item, moderationOpts)}
|
? undefined
|
||||||
style={i === 0 ? {borderTopWidth: 0} : {}}
|
: `/search?q=${encodeURIComponent(query)}`
|
||||||
/>
|
}
|
||||||
))
|
style={{borderBottomWidth: 1}}
|
||||||
) : (
|
/>
|
||||||
<EmptyState message={_(msg`No results found for ${query}`)} />
|
|
||||||
)}
|
{queryMaybeHandle ? (
|
||||||
|
<SearchLinkCard
|
||||||
|
label={_(msg`Go to @${queryMaybeHandle}`)}
|
||||||
|
to={`/profile/${queryMaybeHandle}`}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{searchResults.map(item => (
|
||||||
|
<SearchProfileCard
|
||||||
|
key={item.did}
|
||||||
|
profile={item}
|
||||||
|
moderation={moderateProfile(item, moderationOpts)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<View style={{height: 200}} />
|
<View style={{height: 200}} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user