diff --git a/.github/workflows/golang-test-lint.yml b/.github/workflows/golang-test-lint.yml
index 36e28841d5..c8124dbade 100644
--- a/.github/workflows/golang-test-lint.yml
+++ b/.github/workflows/golang-test-lint.yml
@@ -21,7 +21,7 @@ jobs:
with:
go-version: '1.22'
- name: Dummy Static Files
- run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt
+ run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Check
run: cd bskyweb/ && make check
- name: Build (binary)
@@ -38,6 +38,6 @@ jobs:
with:
go-version: '1.22'
- name: Dummy Static Files
- run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt
+ run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Lint
run: cd bskyweb/ && make lint
diff --git a/.prettierignore b/.prettierignore
index 641a1b8bfc..8ccbae2148 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1,4 @@
-# Ignore everything except JS-ey code.
+# Ignore everything except JS-ey or CSS code.
# Based on https://stackoverflow.com/a/70715829/458193
*
!**/*.js
@@ -7,6 +7,8 @@
!**/*.tsx
!*/
+!**/*.css
+
# More specific ignores go below.
.expo
android
diff --git a/Dockerfile.embedr b/Dockerfile.embedr
index 9ff04aa5c7..663cbcfc51 100644
--- a/Dockerfile.embedr
+++ b/Dockerfile.embedr
@@ -40,6 +40,7 @@ RUN find ./bskyweb/embedr-static && find ./bskyweb/embedr-templates && find ./bs
# hack around issue with empty directory and go:embed
RUN touch bskyweb/static/js/empty.txt
+RUN touch bskyweb/static/css/empty.txt
RUN touch bskyweb/static/media/empty.txt
#
diff --git a/bskyembed/src/index.css b/bskyembed/src/index.css
index 23457ec28d..22b2b8be5c 100644
--- a/bskyembed/src/index.css
+++ b/bskyembed/src/index.css
@@ -4,4 +4,4 @@
.break-word {
word-break: break-word;
-}
\ No newline at end of file
+}
diff --git a/bskyweb/.gitignore b/bskyweb/.gitignore
index 05b3ad7ab5..a63a381f94 100644
--- a/bskyweb/.gitignore
+++ b/bskyweb/.gitignore
@@ -10,6 +10,10 @@ static/js/*.js
static/js/*.map
static/js/*.js.LICENSE.txt
static/js/empty.txt
+static/css/*.css
+static/css/*.map
+static/css/*.css.LICENSE.txt
+static/css/empty.txt
static/media/*.png
static/media/empty.txt
templates/scripts.html
diff --git a/bskyweb/static/css/.gitkeep b/bskyweb/static/css/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html
index 609c17c7ce..eaa31aa4a3 100644
--- a/bskyweb/templates/base.html
+++ b/bskyweb/templates/base.html
@@ -32,387 +32,6 @@
-->
-
{% include "scripts.html" %}
diff --git a/package.json b/package.json
index f4c63f9590..245c095f19 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"web": "expo start --web",
"use-build-number": "./scripts/useBuildNumberEnv.sh",
"use-build-number-with-bump": "./scripts/useBuildNumberEnvWithBump.sh",
- "build-web": "expo export:web && node ./scripts/post-web-build.js && cp -v ./web-build/static/js/*.* ./bskyweb/static/js/ && cp -v ./web-build/static/media/* ./bskyweb/static/media/",
+ "build-web": "expo export:web && node ./scripts/post-web-build.js",
"build-all": "yarn intl:build && yarn use-build-number-with-bump eas build --platform all",
"build-ios": "yarn use-build-number-with-bump eas build -p ios",
"build-android": "yarn use-build-number-with-bump eas build -p android",
@@ -339,7 +339,9 @@
},
"lint-staged": {
"*{.js,.jsx,.ts,.tsx}": [
- "eslint --cache --fix",
+ "eslint --cache --fix"
+ ],
+ "*{.js,.jsx,.ts,.tsx,.css}": [
"prettier --cache --write --ignore-unknown"
]
}
diff --git a/scripts/post-web-build.js b/scripts/post-web-build.js
index baaa7cb8b7..7bbee38554 100644
--- a/scripts/post-web-build.js
+++ b/scripts/post-web-build.js
@@ -20,7 +20,30 @@ console.log(`Writing ${templateFile}`)
const outputFile = entrypoints
.map(name => {
const file = path.basename(name)
- return ``
+ const ext = path.extname(file)
+
+ if (ext === '.js') {
+ return ``
+ }
+ if (ext === '.css') {
+ return ``
+ }
+
+ return ''
})
.join('\n')
fs.writeFileSync(templateFile, outputFile)
+
+function copyFiles(sourceDir, targetDir) {
+ const files = fs.readdirSync(path.join(projectRoot, sourceDir))
+ files.forEach(file => {
+ const sourcePath = path.join(projectRoot, sourceDir, file)
+ const targetPath = path.join(projectRoot, targetDir, file)
+ fs.copyFileSync(sourcePath, targetPath)
+ console.log(`Copied ${sourcePath} to ${targetPath}`)
+ })
+}
+
+copyFiles('web-build/static/js', 'bskyweb/static/js')
+copyFiles('web-build/static/css', 'bskyweb/static/css')
+copyFiles('web-build/static/media', 'bskyweb/static/media')
diff --git a/src/App.web.tsx b/src/App.web.tsx
index c81ed10d33..1664812d08 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -1,5 +1,6 @@
-import 'lib/sentry' // must be near top
-import 'view/icons'
+import '#/lib/sentry' // must be near top
+import '#/view/icons'
+import './style.css'
import React, {useEffect, useState} from 'react'
import {KeyboardProvider} from 'react-native-keyboard-controller'
diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts
index 08cfd9f42d..b11ce939f8 100644
--- a/src/alf/fonts.ts
+++ b/src/alf/fonts.ts
@@ -1,5 +1,3 @@
-import {useFonts as defaultUseFonts} from 'expo-font'
-
import {isWeb} from '#/platform/detection'
import {Device, device} from '#/storage'
@@ -34,39 +32,6 @@ export function setFontFamily(fontFamily: Device['fontFamily']) {
device.set(['fontFamily'], fontFamily)
}
-/*
- * IMPORTANT: This is unused. Expo statically extracts these fonts, but we load
- * them manually so that we can parallelize the loading along with the JS
- * bundle.
- *
- * See `#/alf/util/useFonts` for the actually used hooks.
- *
- * All used fonts MUST be configured here. Unused fonts are commented out, but
- * the files are there if we need them.
- */
-export function DO_NOT_USE() {
- return defaultUseFonts({
- // 'Inter-Thin': require('../../assets/fonts/inter/Inter-Thin.otf'),
- // 'Inter-ThinItalic': require('../../assets/fonts/inter/Inter-ThinItalic.otf'),
- // 'Inter-ExtraLight': require('../../assets/fonts/inter/Inter-ExtraLight.otf'),
- // 'Inter-ExtraLightItalic': require('../../assets/fonts/inter/Inter-ExtraLightItalic.otf'),
- // 'Inter-Light': require('../../assets/fonts/inter/Inter-Light.otf'),
- // 'Inter-LightItalic': require('../../assets/fonts/inter/Inter-LightItalic.otf'),
- 'Inter-Regular': require('../../assets/fonts/inter/Inter-Regular.otf'),
- 'Inter-Italic': require('../../assets/fonts/inter/Inter-Italic.otf'),
- // 'Inter-Medium': require('../../assets/fonts/inter/Inter-Medium.otf'),
- // 'Inter-MediumItalic': require('../../assets/fonts/inter/Inter-MediumItalic.otf'),
- 'Inter-SemiBold': require('../../assets/fonts/inter/Inter-SemiBold.otf'),
- 'Inter-SemiBoldItalic': require('../../assets/fonts/inter/Inter-SemiBoldItalic.otf'),
- // 'Inter-Bold': require('../../assets/fonts/inter/Inter-Bold.otf'),
- // 'Inter-BoldItalic': require('../../assets/fonts/inter/Inter-BoldItalic.otf'),
- 'Inter-ExtraBold': require('../../assets/fonts/inter/Inter-ExtraBold.otf'),
- 'Inter-ExtraBoldItalic': require('../../assets/fonts/inter/Inter-ExtraBoldItalic.otf'),
- // 'Inter-Black': require('../../assets/fonts/inter/Inter-Black.otf'),
- // 'Inter-BlackItalic': require('../../assets/fonts/inter/Inter-BlackItalic.otf'),
- })
-}
-
/*
* Unused fonts are commented out, but the files are there if we need them.
*/
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index f5d2247f9f..9f7ec5c673 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -183,7 +183,7 @@ export function createThemes({
} as const
const darkPalette: Palette = {
- white: color.gray_0,
+ white: color.gray_25,
black: color.trueBlack,
contrast_25: color.gray_975,
diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx
index 9f4491aafc..03b8ece6b1 100644
--- a/src/components/LabelingServiceCard/index.tsx
+++ b/src/components/LabelingServiceCard/index.tsx
@@ -52,13 +52,14 @@ export function Title({value}: {value: string}) {
}
export function Description({value, handle}: {value?: string; handle: string}) {
+ const {_} = useLingui()
return value ? (
) : (
-
- By {sanitizeHandle(handle, '@')}
+
+ {_(msg`By ${sanitizeHandle(handle, '@')}`)}
)
}
diff --git a/src/components/ListCard.tsx b/src/components/ListCard.tsx
index 35bbac53e8..ed5838fb04 100644
--- a/src/components/ListCard.tsx
+++ b/src/components/ListCard.tsx
@@ -7,7 +7,8 @@ import {
moderateUserList,
ModerationUI,
} from '@atproto/api'
-import {Trans} from '@lingui/macro'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {sanitizeHandle} from '#/lib/strings/handles'
@@ -111,6 +112,7 @@ export function TitleAndByline({
modUi?: ModerationUI
}) {
const t = useTheme()
+ const {_} = useLingui()
const {currentAccount} = useSession()
return (
@@ -140,15 +142,12 @@ export function TitleAndByline({
{creator && (
- {purpose === MODLIST ? (
-
- Moderation list by {sanitizeHandle(creator.handle, '@')}
-
- ) : (
- List by {sanitizeHandle(creator.handle, '@')}
- )}
+ {purpose === MODLIST
+ ? _(msg`Moderation list by ${sanitizeHandle(creator.handle, '@')}`)
+ : _(msg`List by ${sanitizeHandle(creator.handle, '@')}`)}
)}
diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx
index aff11c4f37..50b34ba99d 100644
--- a/src/components/ProfileCard.tsx
+++ b/src/components/ProfileCard.tsx
@@ -181,6 +181,7 @@ export function NameAndHandle({
{name}
{handle}
diff --git a/src/components/StarterPack/StarterPackCard.tsx b/src/components/StarterPack/StarterPackCard.tsx
index 687a7487f3..ead9c92480 100644
--- a/src/components/StarterPack/StarterPackCard.tsx
+++ b/src/components/StarterPack/StarterPackCard.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {AppBskyGraphDefs, AppBskyGraphStarterpack, AtUri} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
+import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
@@ -69,13 +69,10 @@ export function Card({
{record.name}
-
-
- Starter pack by{' '}
- {creator?.did === currentAccount?.did
- ? _(msg`you`)
- : `@${sanitizeHandle(creator.handle)}`}
-
+
+ {creator?.did === currentAccount?.did
+ ? _(msg`Starter pack by you`)
+ : _(msg`Starter pack by ${sanitizeHandle(creator.handle, '@')}`)}
diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx
index 7c13241e2c..501e23872f 100644
--- a/src/components/Typography.tsx
+++ b/src/components/Typography.tsx
@@ -159,9 +159,7 @@ export function Text({
}
if (emoji && !childIsString(children)) {
- throw new Error(
- 'Text: when , children can only be strings.',
- )
+ logger.error('Text: when , children can only be strings.')
}
}
diff --git a/src/lib/themes.ts b/src/lib/themes.ts
index 5030799932..eb11872fa3 100644
--- a/src/lib/themes.ts
+++ b/src/lib/themes.ts
@@ -325,11 +325,11 @@ export const darkTheme: Theme = {
textInverted: colors.green2,
},
inverted: {
- background: lightPalette.white,
+ background: darkPalette.white,
backgroundLight: lightPalette.contrast_50,
text: lightPalette.black,
textLight: lightPalette.contrast_700,
- textInverted: lightPalette.white,
+ textInverted: darkPalette.white,
link: lightPalette.primary_500,
border: lightPalette.contrast_100,
borderDark: lightPalette.contrast_200,
diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx
index 0344f1a234..ba869b6626 100644
--- a/src/screens/Profile/Header/Handle.tsx
+++ b/src/screens/Profile/Header/Handle.tsx
@@ -1,11 +1,12 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
-import {Trans} from '@lingui/macro'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {isInvalidHandle} from '#/lib/strings/handles'
+import {isIOS} from '#/platform/detection'
import {Shadow} from '#/state/cache/types'
-import {isInvalidHandle} from 'lib/strings/handles'
-import {isIOS} from 'platform/detection'
import {atoms as a, useTheme, web} from '#/alf'
import {NewskieDialog} from '#/components/NewskieDialog'
import {Text} from '#/components/Typography'
@@ -18,6 +19,7 @@ export function ProfileHeaderHandle({
disableTaps?: boolean
}) {
const t = useTheme()
+ const {_} = useLingui()
const invalidHandle = isInvalidHandle(profile.handle)
const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy
return (
@@ -33,6 +35,7 @@ export function ProfileHeaderHandle({
) : undefined}
- {invalidHandle ? ⚠Invalid Handle : `@${profile.handle}`}
+ {invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
)
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000000..ebb0471584
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,399 @@
+@font-face {
+ font-family: 'Inter-Regular';
+ src: local('Inter-Regular'),
+ url(/assets/fonts/inter/Inter-Regular.otf) format('font/otf');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: 'Inter-Italic';
+ src: local('Inter-Italic'),
+ url(/assets/fonts/inter/Inter-Italic.otf) format('font/otf');
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+/*
+@font-face {
+ font-family: "Inter-Medium";
+ src: local("Inter-Medium"), url(/assets/fonts/inter/Inter-Medium.otf) format("font/otf");
+ font-weight: 500;
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Inter-MediumItalic";
+ src: local("Inter-MediumItalic"), url(/assets/fonts/inter/Inter-MediumItalic.otf) format("font/otf");
+ font-weight: 500;
+ font-style: italic;
+ font-display: swap;
+}
+*/
+@font-face {
+ font-family: 'Inter-SemiBold';
+ src: local('Inter-SemiBold'),
+ url(/assets/fonts/inter/Inter-SemiBold.otf) format('font/otf');
+ font-weight: 600;
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: 'Inter-SemiBoldItalic';
+ src: local('Inter-SemiBoldItalic'),
+ url(/assets/fonts/inter/Inter-SemiBoldItalic.otf) format('font/otf');
+ font-weight: 600;
+ font-style: italic;
+ font-display: swap;
+}
+/*
+@font-face {
+ font-family: "Inter-Bold";
+ src: local("Inter-Bold"), url(/assets/fonts/inter/Inter-Bold.otf) format("font/otf");
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Inter-BoldItalic";
+ src: local("Inter-BoldItalic"), url(/assets/fonts/inter/Inter-BoldItalic.otf) format("font/otf");
+ font-weight: 700;
+ font-style: italic;
+ font-display: swap;
+}
+*/
+@font-face {
+ font-family: 'Inter-ExtraBold';
+ src: local('Inter-ExtraBold'),
+ url(/assets/fonts/inter/Inter-ExtraBold.otf) format('font/otf');
+ font-weight: 800;
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: 'Inter-ExtraBoldItalic';
+ src: local('Inter-ExtraBoldItalic'),
+ url(/assets/fonts/inter/Inter-ExtraBoldItalic.otf) format('font/otf');
+ font-weight: 800;
+ font-style: italic;
+ font-display: swap;
+}
+/*
+@font-face {
+ font-family: "Inter-Black";
+ src: local("Inter-Black"), url(/assets/fonts/inter/Inter-Black.otf) format("font/otf");
+ font-weight: 900;
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Inter-BlackItalic";
+ src: local("Inter-BlackItalic"), url(/assets/fonts/inter/Inter-BlackItalic.otf) format("font/otf");
+ font-weight: 900;
+ font-style: italic;
+ font-display: swap;
+}
+*/
+
+/**
+ * Extend the react-native-web reset:
+ * https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js
+ */
+html,
+body,
+#root {
+ width: 100%;
+ /* To smooth any scrolling behavior */
+ -webkit-overflow-scrolling: touch;
+ margin: 0px;
+ padding: 0px;
+ /* Allows content to fill the viewport and go beyond the bottom */
+ min-height: 100%;
+}
+#root {
+ flex-shrink: 0;
+ flex-basis: auto;
+ flex-grow: 1;
+ display: flex;
+ flex: 1;
+}
+
+html {
+ /* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
+ -webkit-text-size-adjust: 100%;
+ height: calc(100% + env(safe-area-inset-top));
+ scrollbar-gutter: stable both-edges;
+}
+html,
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ 'Liberation Sans', Helvetica, Arial, sans-serif;
+}
+
+#preload {
+ width: 100px;
+ position: fixed;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+}
+
+/* Buttons and inputs have a font set by UA, so we'll have to reset that */
+button,
+input,
+textarea {
+ font: inherit;
+ line-height: inherit;
+}
+
+/* Color theming */
+/* Default will always be white */
+:root {
+ --text: black;
+ --background: white;
+ --backgroundLight: hsl(211, 20%, 95%);
+}
+/* This gives us a black background when system is dark and we have not loaded the theme/color scheme values in JS */
+@media (prefers-color-scheme: dark) {
+ :root {
+ --text: white;
+ --background: black;
+ --backgroundLight: hsl(211, 20%, 20%);
+ color-scheme: dark;
+ }
+}
+
+/* Overwrite those preferences with the selected theme */
+html.theme--light {
+ --text: black;
+ --background: white;
+ --backgroundLight: hsl(211, 20%, 95%);
+}
+html.theme--dark {
+ --text: white;
+ --background: black;
+ --backgroundLight: hsl(211, 20%, 20%);
+ color-scheme: dark;
+}
+html.theme--dim {
+ --text: white;
+ --background: hsl(211, 20%, 4%);
+ --backgroundLight: hsl(211, 20%, 10%);
+ color-scheme: dark;
+}
+
+/* Remove autofill styles on Webkit */
+input:autofill,
+input:-webkit-autofill,
+input:-webkit-autofill:hover,
+input:-webkit-autofill:focus,
+input:-webkit-autofill:active {
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: var(--text);
+ transition: background-color 5000s ease-in-out 0s;
+ box-shadow: inset 0 0 20px 20px var(--background);
+ background: var(--background);
+ color: var(--text);
+}
+/* Force left-align date/time inputs on iOS mobile */
+input::-webkit-date-and-time-value {
+ text-align: left;
+}
+
+body {
+ display: flex;
+ /* Allows you to scroll below the viewport; default value is visible */
+ overflow-y: auto;
+ overscroll-behavior-y: none;
+ text-rendering: optimizeLegibility;
+ background-color: var(--background);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -ms-overflow-style: scrollbar;
+ font-synthesis-weight: none;
+}
+
+/* Remove default link styling */
+a {
+ color: inherit;
+}
+a[role='link']:hover {
+ text-decoration: underline;
+}
+a[role='link'][data-no-underline='1']:hover {
+ text-decoration: none;
+}
+
+/* Styling hacks */
+*[data-word-wrap] {
+ word-break: break-word;
+}
+*[data-stable-gutters] {
+ scrollbar-gutter: stable both-edges;
+}
+
+/* ProseMirror */
+.ProseMirror {
+ font: 18px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ 'Liberation Sans', Helvetica, Arial, sans-serif;
+ min-height: 140px;
+}
+.ProseMirror-dark {
+ color: white;
+}
+.ProseMirror p {
+ margin: 0;
+}
+.ProseMirror p.is-editor-empty:first-child::before {
+ color: #8d8e96;
+ content: attr(data-placeholder);
+ float: left;
+ height: 0;
+ pointer-events: none;
+}
+.ProseMirror .mention {
+ color: #0085ff;
+}
+.ProseMirror a,
+.ProseMirror .autolink {
+ color: #0085ff;
+}
+/* OLLIE: TODO -- this is not accessible */
+/* Remove focus state on inputs */
+.ProseMirror-focused {
+ outline: 0;
+}
+textarea:focus,
+input:focus {
+ outline: 0;
+}
+.tippy-content .items {
+ width: fit-content;
+}
+
+/* Tooltips */
+[data-tooltip] {
+ position: relative;
+ z-index: 10;
+}
+[data-tooltip]::after {
+ content: attr(data-tooltip);
+ display: none;
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ transform: translateY(100%) translateY(8px) translateX(-50%);
+ padding: 4px 10px;
+ border-radius: 10px;
+ background: var(--backgroundLight);
+ color: var(--text);
+ text-align: center;
+ white-space: nowrap;
+ font-size: 12px;
+ z-index: 10;
+}
+[data-tooltip]::before {
+ content: '';
+ display: none;
+ position: absolute;
+ border-bottom: 6px solid var(--backgroundLight);
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ bottom: 0;
+ left: 50%;
+ transform: translateY(100%) translateY(2px) translateX(-50%);
+ z-index: 10;
+}
+[data-tooltip]:hover::after,
+[data-tooltip]:hover::before {
+ display: block;
+}
+
+/* NativeDropdown component */
+.radix-dropdown-item:focus,
+.nativeDropdown-item:focus {
+ outline: none;
+}
+
+/* Spinner component */
+@keyframes rotate {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+.rotate-500ms {
+ position: absolute;
+ inset: 0;
+ animation: rotate 500ms linear infinite;
+}
+
+@keyframes avatarHoverFadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes avatarHoverFadeOut {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+.force-no-clicks > *,
+.force-no-clicks * {
+ pointer-events: none !important;
+}
+
+input[type='range'][orient='vertical'] {
+ writing-mode: vertical-lr;
+ direction: rtl;
+ appearance: slider-vertical;
+ width: 16px;
+ vertical-align: bottom;
+ -webkit-appearance: none;
+ appearance: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+input[type='range'][orient='vertical']::-webkit-slider-runnable-track {
+ background: white;
+ height: 100%;
+ width: 4px;
+ border-radius: 4px;
+}
+
+input[type='range'][orient='vertical']::-moz-range-track {
+ background: white;
+ height: 100%;
+ width: 4px;
+ border-radius: 4px;
+}
+
+input[type='range']::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ border-radius: 50%;
+ background-color: white;
+ height: 16px;
+ width: 16px;
+ margin-left: -6px;
+}
+
+input[type='range'][orient='vertical']::-moz-range-thumb {
+ border: none;
+ border-radius: 50%;
+ background-color: white;
+ height: 16px;
+ width: 16px;
+ margin-left: -6px;
+}
diff --git a/src/view/com/pager/TabBar.tsx b/src/view/com/pager/TabBar.tsx
index 59bb77e367..83de3775c7 100644
--- a/src/view/com/pager/TabBar.tsx
+++ b/src/view/com/pager/TabBar.tsx
@@ -1,9 +1,9 @@
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {LayoutChangeEvent, ScrollView, StyleSheet, View} from 'react-native'
+import {usePalette} from '#/lib/hooks/usePalette'
+import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {isNative} from '#/platform/detection'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {PressableWithHover} from '../util/PressableWithHover'
import {Text} from '../util/text/Text'
import {DraggableScrollView} from './DraggableScrollView'
@@ -131,13 +131,14 @@ export function TabBar({
(itemRefs.current[i] = node)}
+ ref={node => (itemRefs.current[i] = node as any)}
onLayout={e => onItemLayout(e, i)}
style={styles.item}
hoverStyle={pal.viewLight}
onPress={() => onPressItem(i)}>
-
+
{sanitizeHandle(post.author.handle, '@')}
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx
index 611e7957cd..eab8611dd4 100644
--- a/src/view/com/profile/ProfileCard.tsx
+++ b/src/view/com/profile/ProfileCard.tsx
@@ -113,7 +113,7 @@ export function ProfileCard({
moderation.ui('displayName'),
)}
-
+
{sanitizeHandle(profile.handle, '@')}
}
-export const PressableWithHover = forwardRef(function PressableWithHoverImpl(
- {
- children,
- style,
- hoverStyle,
- ...props
- }: PropsWithChildren,
- ref: Ref,
+export const PressableWithHover = forwardRef<
+ View,
+ PropsWithChildren
+>(function PressableWithHoverImpl(
+ {children, style, hoverStyle, ...props},
+ ref,
) {
- const [isHovering, setIsHovering] = useState(false)
-
- const onHoverIn = useCallback(() => setIsHovering(true), [setIsHovering])
- const onHoverOut = useCallback(() => setIsHovering(false), [setIsHovering])
- style =
- typeof style !== 'function' && isHovering
- ? addStyle(style, hoverStyle)
- : style
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
return (
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx
index 6b509d09a3..8ffe482a8f 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/ControlButton.tsx
@@ -1,8 +1,8 @@
import React from 'react'
import {SvgProps} from 'react-native-svg'
-import {atoms as a, useTheme} from '#/alf'
-import {Button} from '#/components/Button'
+import {atoms as a, useTheme, web} from '#/alf'
+import {PressableWithHover} from '../../../PressableWithHover'
export function ControlButton({
active,
@@ -21,19 +21,21 @@ export function ControlButton({
}) {
const t = useTheme()
return (
-
+
)
}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
index 5bd7e0d179..2d1427347d 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx
@@ -358,9 +358,8 @@ export function Controls({
style={[
a.flex_1,
a.px_xs,
- a.pt_2xs,
- a.pb_md,
- a.gap_md,
+ a.pb_sm,
+ a.gap_sm,
a.flex_row,
a.align_center,
]}>
@@ -373,7 +372,11 @@ export function Controls({
onPress={onPressPlayPause}
/>
-
+
{formatTime(currentTime)} / {formatTime(duration)}
{hasSubtitleTrack && (
diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx
index 9e4f7e5f5d..3d885480cc 100644
--- a/src/view/com/util/text/Text.tsx
+++ b/src/view/com/util/text/Text.tsx
@@ -56,9 +56,7 @@ export function Text({
}
if (emoji && !childIsString(children)) {
- throw new Error(
- 'Text: when , children can only be strings.',
- )
+ logger.error('Text: when , children can only be strings.')
}
}
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index e1e412648b..07d762c0fe 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -959,6 +959,7 @@ function SearchHistory({
accessibilityIgnoresInvertColors
/>
{profile.displayName || profile.handle}
diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx
index 1ba2d3f3db..b43dbcce32 100644
--- a/src/view/shell/desktop/Search.tsx
+++ b/src/view/shell/desktop/Search.tsx
@@ -16,19 +16,19 @@ import {useLingui} from '@lingui/react'
import {StackActions, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
+import {usePalette} from '#/lib/hooks/usePalette'
import {makeProfileLink} from '#/lib/routes/links'
+import {NavigationProp} from '#/lib/routes/types'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {s} from '#/lib/styles'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
-import {usePalette} from 'lib/hooks/usePalette'
-import {NavigationProp} from 'lib/routes/types'
-import {precacheProfile} from 'state/queries/profile'
+import {precacheProfile} from '#/state/queries/profile'
+import {SearchInput} from '#/view/com/util/forms/SearchInput'
import {Link} from '#/view/com/util/Link'
+import {Text} from '#/view/com/util/text/Text'
import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {SearchInput} from 'view/com/util/forms/SearchInput'
-import {Text} from 'view/com/util/text/Text'
import {atoms as a} from '#/alf'
let SearchLinkCard = ({
@@ -126,6 +126,7 @@ let SearchProfileCard = ({
/>
-->
-
-