diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html
index 198e3baa76..e29e4032bb 100644
--- a/bskyweb/templates/base.html
+++ b/bskyweb/templates/base.html
@@ -211,7 +211,7 @@
-
+
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index f75e8ffe0b..18f492d6e7 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -176,43 +176,59 @@ export const atoms = {
},
text_2xs: {
fontSize: tokens.fontSize._2xs,
+ letterSpacing: 0.25,
},
text_xs: {
fontSize: tokens.fontSize.xs,
+ letterSpacing: 0.25,
},
text_sm: {
fontSize: tokens.fontSize.sm,
+ letterSpacing: 0.25,
},
text_md: {
fontSize: tokens.fontSize.md,
+ letterSpacing: 0.25,
},
text_lg: {
fontSize: tokens.fontSize.lg,
+ letterSpacing: 0.25,
},
text_xl: {
fontSize: tokens.fontSize.xl,
+ letterSpacing: 0.25,
},
text_2xl: {
fontSize: tokens.fontSize._2xl,
+ letterSpacing: 0.25,
},
text_3xl: {
fontSize: tokens.fontSize._3xl,
+ letterSpacing: 0.25,
},
text_4xl: {
fontSize: tokens.fontSize._4xl,
+ letterSpacing: 0.25,
},
text_5xl: {
fontSize: tokens.fontSize._5xl,
+ letterSpacing: 0.25,
},
leading_tight: {
lineHeight: 1.15,
},
leading_snug: {
- lineHeight: 1.25,
+ lineHeight: 1.3,
},
leading_normal: {
lineHeight: 1.5,
},
+ tracking_normal: {
+ letterSpacing: 0,
+ },
+ tracking_wide: {
+ letterSpacing: 0.25,
+ },
font_normal: {
fontWeight: tokens.fontWeight.normal,
},
diff --git a/src/alf/util/useColorModeTheme.ts b/src/alf/util/useColorModeTheme.ts
index 48cf904fe1..4f8921bf9b 100644
--- a/src/alf/util/useColorModeTheme.ts
+++ b/src/alf/util/useColorModeTheme.ts
@@ -13,7 +13,7 @@ export function useColorModeTheme(): ThemeName {
React.useLayoutEffect(() => {
const theme = getThemeName(colorScheme, colorMode, darkTheme)
updateDocument(theme)
- updateSystemBackground(theme)
+ SystemUI.setBackgroundColorAsync(getBackgroundColor(theme))
}, [colorMode, colorScheme, darkTheme])
return React.useMemo(
@@ -42,23 +42,24 @@ function updateDocument(theme: ThemeName) {
if (isWeb && typeof window !== 'undefined') {
// @ts-ignore web only
const html = window.document.documentElement
+ // @ts-ignore web only
+ const meta = window.document.querySelector('meta[name="theme-color"]')
+
// remove any other color mode classes
html.className = html.className.replace(/(theme)--\w+/g, '')
-
html.classList.add(`theme--${theme}`)
+ // set color to 'theme-color' meta tag
+ meta?.setAttribute('content', getBackgroundColor(theme))
}
}
-function updateSystemBackground(theme: ThemeName) {
+function getBackgroundColor(theme: ThemeName): string {
switch (theme) {
case 'light':
- SystemUI.setBackgroundColorAsync(light.atoms.bg.backgroundColor)
- break
+ return light.atoms.bg.backgroundColor
case 'dark':
- SystemUI.setBackgroundColorAsync(dark.atoms.bg.backgroundColor)
- break
+ return dark.atoms.bg.backgroundColor
case 'dim':
- SystemUI.setBackgroundColorAsync(dim.atoms.bg.backgroundColor)
- break
+ return dim.atoms.bg.backgroundColor
}
}
diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts
index b28b9f5a25..f0c7c983a9 100644
--- a/src/components/Dialog/context.ts
+++ b/src/components/Dialog/context.ts
@@ -1,7 +1,11 @@
import React from 'react'
import {useDialogStateContext} from '#/state/dialogs'
-import {DialogContextProps, DialogControlProps} from '#/components/Dialog/types'
+import {
+ DialogContextProps,
+ DialogControlProps,
+ DialogOuterProps,
+} from '#/components/Dialog/types'
export const Context = React.createContext({
close: () => {},
@@ -11,7 +15,7 @@ export function useDialogContext() {
return React.useContext(Context)
}
-export function useDialogControl() {
+export function useDialogControl(): DialogOuterProps['control'] {
const id = React.useId()
const control = React.useRef({
open: () => {},
@@ -30,6 +34,6 @@ export function useDialogControl() {
return {
ref: control,
open: () => control.current.open(),
- close: () => control.current.close(),
+ close: cb => control.current.close(cb),
}
}
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 9132e68de5..27f43afd3a 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -8,7 +8,7 @@ import BottomSheet, {
} from '@gorhom/bottom-sheet'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {useTheme, atoms as a} from '#/alf'
+import {useTheme, atoms as a, flatten} from '#/alf'
import {Portal} from '#/components/Portal'
import {createInput} from '#/components/forms/TextField'
@@ -35,12 +35,30 @@ export function Outer({
const sheetOptions = nativeOptions?.sheet || {}
const hasSnapPoints = !!sheetOptions.snapPoints
const insets = useSafeAreaInsets()
+ const closeCallback = React.useRef<() => void>()
- const open = React.useCallback((i = 0) => {
- sheet.current?.snapToIndex(i)
- }, [])
+ /*
+ * Used to manage open/closed, but index is otherwise handled internally by `BottomSheet`
+ */
+ const [openIndex, setOpenIndex] = React.useState(-1)
- const close = React.useCallback(() => {
+ /*
+ * `openIndex` is the index of the snap point to open the bottom sheet to. If >0, the bottom sheet is open.
+ */
+ const isOpen = openIndex > -1
+
+ const open = React.useCallback(
+ ({index} = {}) => {
+ // can be set to any index of `snapPoints`, but `0` is the first i.e. "open"
+ setOpenIndex(index || 0)
+ },
+ [setOpenIndex],
+ )
+
+ const close = React.useCallback(cb => {
+ if (cb) {
+ closeCallback.current = cb
+ }
sheet.current?.close()
}, [])
@@ -56,78 +74,85 @@ export function Outer({
const onChange = React.useCallback(
(index: number) => {
if (index === -1) {
+ closeCallback.current?.()
+ closeCallback.current = undefined
onClose?.()
+ setOpenIndex(-1)
}
},
- [onClose],
+ [onClose, setOpenIndex],
)
const context = React.useMemo(() => ({close}), [close])
return (
-
- (
-
- )}
- handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
- handleStyle={{display: 'none'}}
- onChange={onChange}>
-
-
- {children}
-
-
-
+ isOpen && (
+
+ (
+
+ )}
+ handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
+ handleStyle={{display: 'none'}}
+ onChange={onChange}>
+
+
+ {children}
+
+
+
+ )
)
}
-// TODO a11y props here, or is that handled by the sheet?
-export function Inner(props: DialogInnerProps) {
+export function Inner({children, style}: DialogInnerProps) {
const insets = useSafeAreaInsets()
return (
- {props.children}
+ {children}
)
}
-export function ScrollableInner(props: DialogInnerProps) {
+export function ScrollableInner({children, style}: DialogInnerProps) {
const insets = useSafeAreaInsets()
return (
- {props.children}
+ {children}
)
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 305c00e97a..79441fb5e1 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -5,11 +5,13 @@ 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 {useTheme, atoms as a, useBreakpoints, web, flatten} from '#/alf'
import {Portal} from '#/components/Portal'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
import {Context} from '#/components/Dialog/context'
+import {Button, ButtonIcon} from '#/components/Button'
+import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
@@ -18,9 +20,9 @@ export {Input} from '#/components/forms/TextField'
const stopPropagation = (e: any) => e.stopPropagation()
export function Outer({
+ children,
control,
onClose,
- children,
}: React.PropsWithChildren) {
const {_} = useLingui()
const t = useTheme()
@@ -147,7 +149,7 @@ export function Inner({
a.rounded_md,
a.w_full,
a.border,
- gtMobile ? a.p_xl : a.p_lg,
+ gtMobile ? a.p_2xl : a.p_xl,
t.atoms.bg,
{
maxWidth: 600,
@@ -156,7 +158,7 @@ export function Inner({
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
shadowRadius: 30,
},
- ...(Array.isArray(style) ? style : [style || {}]),
+ flatten(style),
]}>
{children}
@@ -170,25 +172,28 @@ export function Handle() {
return null
}
-/**
- * TODO(eric) unused rn
- */
-// export function Close() {
-// const {_} = useLingui()
-// const t = useTheme()
-// const {close} = useDialogContext()
-// return (
-//
-//
-//
-// )
-// }
+export function Close() {
+ const {_} = useLingui()
+ const {close} = React.useContext(Context)
+ return (
+
+
+
+ )
+}
diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts
index d36784183c..75ba825ac9 100644
--- a/src/components/Dialog/types.ts
+++ b/src/components/Dialog/types.ts
@@ -1,24 +1,34 @@
import React from 'react'
-import type {ViewStyle, AccessibilityProps} from 'react-native'
+import type {AccessibilityProps} from 'react-native'
import {BottomSheetProps} from '@gorhom/bottom-sheet'
+import {ViewStyleProp} from '#/alf'
+
type A11yProps = Required
export type DialogContextProps = {
close: () => void
}
+export type DialogControlOpenOptions = {
+ /**
+ * NATIVE ONLY
+ *
+ * Optional index of the snap point to open the bottom sheet to. Defaults to
+ * 0, which is the first snap point (i.e. "open").
+ */
+ index?: number
+}
+
export type DialogControlProps = {
- open: (index?: number) => void
- close: () => void
+ open: (options?: DialogControlOpenOptions) => void
+ close: (callback?: () => void) => void
}
export type DialogOuterProps = {
control: {
ref: React.RefObject
- open: (index?: number) => void
- close: () => void
- }
+ } & DialogControlProps
onClose?: () => void
nativeOptions?: {
sheet?: Omit
@@ -26,10 +36,7 @@ export type DialogOuterProps = {
webOptions?: {}
}
-type DialogInnerPropsBase = React.PropsWithChildren<{
- style?: ViewStyle
-}> &
- T
+type DialogInnerPropsBase = React.PropsWithChildren & T
export type DialogInnerProps =
| DialogInnerPropsBase<{
label?: undefined
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index afd30b5ee2..593b0863a7 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -1,9 +1,5 @@
import React from 'react'
-import {
- GestureResponderEvent,
- Linking,
- TouchableWithoutFeedback,
-} from 'react-native'
+import {GestureResponderEvent, Linking} from 'react-native'
import {
useLinkProps,
useNavigation,
@@ -23,7 +19,7 @@ import {
} from '#/lib/strings/url-helpers'
import {useModalControls} from '#/state/modals'
import {router} from '#/routes'
-import {Text} from '#/components/Typography'
+import {Text, TextProps} from '#/components/Typography'
/**
* Only available within a `Link`, since that inherits from `Button`.
@@ -55,11 +51,12 @@ type BaseLinkProps = Pick<
warnOnMismatchingTextChild?: boolean
/**
- * Callback for when the link is pressed.
+ * Callback for when the link is pressed. Prevent default and return `false`
+ * to exit early and prevent navigation.
*
* DO NOT use this for navigation, that's what the `to` prop is for.
*/
- onPress?: (e: GestureResponderEvent) => void
+ onPress?: (e: GestureResponderEvent) => void | false
/**
* Web-only attribute. Sets `download` attr on web.
@@ -86,7 +83,9 @@ export function useLink({
const onPress = React.useCallback(
(e: GestureResponderEvent) => {
- outerOnPress?.(e)
+ const exitEarlyIfFalse = outerOnPress?.(e)
+
+ if (exitEarlyIfFalse === false) return
const requiresWarning = Boolean(
warnOnMismatchingTextChild &&
@@ -217,7 +216,7 @@ export function Link({
}
export type InlineLinkProps = React.PropsWithChildren<
- BaseLinkProps & TextStyleProp
+ BaseLinkProps & TextStyleProp & Pick
>
export function InlineLink({
@@ -228,6 +227,7 @@ export function InlineLink({
style,
onPress: outerOnPress,
download,
+ selectable,
...rest
}: InlineLinkProps) {
const t = useTheme()
@@ -253,43 +253,41 @@ export function InlineLink({
const flattenedStyle = flatten(style)
return (
-
-
- {children}
-
-
+ onBlur={onBlur}
+ onMouseEnter={onHoverIn}
+ onMouseLeave={onHoverOut}
+ accessibilityRole="link"
+ href={href}
+ {...web({
+ hrefAttrs: {
+ target: download ? undefined : isExternal ? 'blank' : undefined,
+ rel: isExternal ? 'noopener noreferrer' : undefined,
+ download,
+ },
+ dataSet: {
+ // default to no underline, apply this ourselves
+ noUnderline: '1',
+ },
+ })}>
+ {children}
+
)
}
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index 2c79d27cf1..411679102b 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -41,7 +41,7 @@ export function Outer({
+ style={[{width: 'auto', maxWidth: 400}]}>
{children}
diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx
index 068ee99e07..c72fcabdd8 100644
--- a/src/components/RichText.tsx
+++ b/src/components/RichText.tsx
@@ -1,9 +1,9 @@
import React from 'react'
import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api'
-import {atoms as a, TextStyleProp} from '#/alf'
+import {atoms as a, TextStyleProp, flatten} from '#/alf'
import {InlineLink} from '#/components/Link'
-import {Text} from '#/components/Typography'
+import {Text, TextProps} from '#/components/Typography'
import {toShortUrl} from 'lib/strings/url-helpers'
import {getAgent} from '#/state/session'
@@ -16,18 +16,20 @@ export function RichText({
numberOfLines,
disableLinks,
resolveFacets = false,
-}: TextStyleProp & {
- value: RichTextAPI | string
- testID?: string
- numberOfLines?: number
- disableLinks?: boolean
- resolveFacets?: boolean
-}) {
+ selectable,
+}: TextStyleProp &
+ Pick & {
+ value: RichTextAPI | string
+ testID?: string
+ numberOfLines?: number
+ disableLinks?: boolean
+ resolveFacets?: boolean
+ }) {
const detected = React.useRef(false)
const [richText, setRichText] = React.useState(() =>
value instanceof RichTextAPI ? value : new RichTextAPI({text: value}),
)
- const styles = [a.leading_normal, style]
+ const styles = [a.leading_snug, flatten(style)]
React.useEffect(() => {
if (!resolveFacets) return
@@ -50,6 +52,7 @@ export function RichText({
if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) {
return (
+ return selectable && isIOS ? (
+
+ ) : (
+
+ )
}
export function createHeadingElement({level}: {level: number}) {
return function HeadingElement({style, ...rest}: TextProps) {
- const t = useTheme()
const attr =
web({
role: 'heading',
'aria-level': level,
}) || {}
- return (
-
- )
+ return
}
}
@@ -78,21 +84,15 @@ export const H4 = createHeadingElement({level: 4})
export const H5 = createHeadingElement({level: 5})
export const H6 = createHeadingElement({level: 6})
export function P({style, ...rest}: TextProps) {
- const t = useTheme()
const attr =
web({
role: 'paragraph',
}) || {}
return (
-
)
}
diff --git a/src/components/icons/Times.tsx b/src/components/icons/Times.tsx
new file mode 100644
index 0000000000..678ac3fcb3
--- /dev/null
+++ b/src/components/icons/Times.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const TimesLarge_Stroke2_Corner0_Rounded = createSinglePathSVG({
+ path: 'M4.293 4.293a1 1 0 0 1 1.414 0L12 10.586l6.293-6.293a1 1 0 1 1 1.414 1.414L13.414 12l6.293 6.293a1 1 0 0 1-1.414 1.414L12 13.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L10.586 12 4.293 5.707a1 1 0 0 1 0-1.414Z',
+})
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index aec8338d03..c8e5273d47 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -3,9 +3,8 @@ import {Insets, Platform} from 'react-native'
export const LOCAL_DEV_SERVICE =
Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583'
export const STAGING_SERVICE = 'https://staging.bsky.dev'
-export const PROD_SERVICE = 'https://bsky.social'
-export const DEFAULT_SERVICE = PROD_SERVICE
-
+export const BSKY_SERVICE = 'https://bsky.social'
+export const DEFAULT_SERVICE = BSKY_SERVICE
const HELP_DESK_LANG = 'en-us'
export const HELP_DESK_URL = `https://blueskyweb.zendesk.com/hc/${HELP_DESK_LANG}`
@@ -36,92 +35,12 @@ export const MAX_GRAPHEME_LENGTH = 300
// but increasing limit per user feedback
export const MAX_ALT_TEXT = 1000
-export function IS_LOCAL_DEV(url: string) {
- return url.includes('localhost')
+export function IS_PROD_SERVICE(url?: string) {
+ return url && url !== STAGING_SERVICE && url !== LOCAL_DEV_SERVICE
}
-export function IS_STAGING(url: string) {
- return url.startsWith('https://staging.bsky.dev')
-}
-
-export function IS_PROD(url: string) {
- // NOTE
- // until open federation, "production" is defined as the main server
- // this definition will not work once federation is enabled!
- // -prf
- return (
- url.startsWith('https://bsky.social') ||
- url.startsWith('https://api.bsky.app') ||
- /bsky\.network\/?$/.test(url)
- )
-}
-
-export const PROD_TEAM_HANDLES = [
- 'jay.bsky.social',
- 'pfrazee.com',
- 'divy.zone',
- 'dholms.xyz',
- 'why.bsky.world',
- 'iamrosewang.bsky.social',
-]
-export const STAGING_TEAM_HANDLES = [
- 'arcalinea.staging.bsky.dev',
- 'paul.staging.bsky.dev',
- 'paul2.staging.bsky.dev',
-]
-export const DEV_TEAM_HANDLES = ['alice.test', 'bob.test', 'carla.test']
-
-export function TEAM_HANDLES(serviceUrl: string) {
- if (serviceUrl.includes('localhost')) {
- return DEV_TEAM_HANDLES
- } else if (serviceUrl.includes('staging')) {
- return STAGING_TEAM_HANDLES
- } else {
- return PROD_TEAM_HANDLES
- }
-}
-
-export const STAGING_DEFAULT_FEED = (rkey: string) =>
- `at://did:plc:wqzurwm3kmaig6e6hnc2gqwo/app.bsky.feed.generator/${rkey}`
export const PROD_DEFAULT_FEED = (rkey: string) =>
`at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/${rkey}`
-export async function DEFAULT_FEEDS(
- serviceUrl: string,
- resolveHandle: (name: string) => Promise,
-) {
- // TODO: remove this when the test suite no longer relies on it
- if (IS_LOCAL_DEV(serviceUrl)) {
- // local dev
- const aliceDid = await resolveHandle('alice.test')
- return {
- pinned: [
- `at://${aliceDid}/app.bsky.feed.generator/alice-favs`,
- `at://${aliceDid}/app.bsky.feed.generator/alice-favs2`,
- ],
- saved: [
- `at://${aliceDid}/app.bsky.feed.generator/alice-favs`,
- `at://${aliceDid}/app.bsky.feed.generator/alice-favs2`,
- ],
- }
- } else if (IS_STAGING(serviceUrl)) {
- // staging
- return {
- pinned: [STAGING_DEFAULT_FEED('whats-hot')],
- saved: [
- STAGING_DEFAULT_FEED('bsky-team'),
- STAGING_DEFAULT_FEED('with-friends'),
- STAGING_DEFAULT_FEED('whats-hot'),
- STAGING_DEFAULT_FEED('hot-classic'),
- ],
- }
- } else {
- // production
- return {
- pinned: [PROD_DEFAULT_FEED('whats-hot')],
- saved: [PROD_DEFAULT_FEED('whats-hot')],
- }
- }
-}
export const POST_IMG_MAX = {
width: 2000,
@@ -135,13 +54,11 @@ export const STAGING_LINK_META_PROXY =
export const PROD_LINK_META_PROXY = 'https://cardyb.bsky.app/v1/extract?url='
export function LINK_META_PROXY(serviceUrl: string) {
- if (IS_LOCAL_DEV(serviceUrl)) {
- return STAGING_LINK_META_PROXY
- } else if (IS_STAGING(serviceUrl)) {
- return STAGING_LINK_META_PROXY
- } else {
+ if (IS_PROD_SERVICE(serviceUrl)) {
return PROD_LINK_META_PROXY
}
+
+ return STAGING_LINK_META_PROXY
}
export const STATUS_PAGE_URL = 'https://status.bsky.app/'
diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts
index 21a575b91c..1cf3b12937 100644
--- a/src/lib/strings/embed-player.ts
+++ b/src/lib/strings/embed-player.ts
@@ -343,45 +343,45 @@ export function parseEmbedPlayerFromUrl(
}
}
-export function getPlayerHeight({
+export function getPlayerAspect({
type,
- width,
hasThumb,
+ width,
}: {
type: EmbedPlayerParams['type']
- width: number
hasThumb: boolean
-}) {
- if (!hasThumb) return (width / 16) * 9
+ width: number
+}): {aspectRatio?: number; height?: number} {
+ if (!hasThumb) return {aspectRatio: 16 / 9}
switch (type) {
case 'youtube_video':
case 'twitch_video':
case 'vimeo_video':
- return (width / 16) * 9
+ return {aspectRatio: 16 / 9}
case 'youtube_short':
if (SCREEN_HEIGHT < 600) {
- return ((width / 9) * 16) / 1.75
+ return {aspectRatio: (9 / 16) * 1.75}
} else {
- return ((width / 9) * 16) / 1.5
+ return {aspectRatio: (9 / 16) * 1.5}
}
case 'spotify_album':
case 'apple_music_album':
case 'apple_music_playlist':
case 'spotify_playlist':
case 'soundcloud_set':
- return 380
+ return {height: 380}
case 'spotify_song':
if (width <= 300) {
- return 155
+ return {height: 155}
}
- return 232
+ return {height: 232}
case 'soundcloud_track':
- return 165
+ return {height: 165}
case 'apple_music_song':
- return 150
+ return {height: 150}
default:
- return width
+ return {aspectRatio: 16 / 9}
}
}
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
index 8a71718c8d..ef341154df 100644
--- a/src/lib/strings/url-helpers.ts
+++ b/src/lib/strings/url-helpers.ts
@@ -1,5 +1,5 @@
import {AtUri} from '@atproto/api'
-import {PROD_SERVICE} from 'lib/constants'
+import {BSKY_SERVICE} from 'lib/constants'
import TLDs from 'tlds'
import psl from 'psl'
@@ -28,7 +28,7 @@ export function makeRecordUri(
export function toNiceDomain(url: string): string {
try {
const urlp = new URL(url)
- if (`https://${urlp.host}` === PROD_SERVICE) {
+ if (`https://${urlp.host}` === BSKY_SERVICE) {
return 'Bluesky Social'
}
return urlp.host ? urlp.host : url
diff --git a/src/screens/Onboarding/StepTopicalFeeds.tsx b/src/screens/Onboarding/StepTopicalFeeds.tsx
index 4a2210853f..636565e340 100644
--- a/src/screens/Onboarding/StepTopicalFeeds.tsx
+++ b/src/screens/Onboarding/StepTopicalFeeds.tsx
@@ -3,7 +3,6 @@ import {View} from 'react-native'
import {useLingui} from '@lingui/react'
import {msg, Trans} from '@lingui/macro'
-import {IS_PROD} from '#/env'
import {atoms as a} from '#/alf'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded as ListMagnifyingGlass} from '#/components/icons/ListMagnifyingGlass'
@@ -22,21 +21,28 @@ import {
import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard'
import {aggregateInterestItems} from '#/screens/Onboarding/util'
import {IconCircle} from '#/components/IconCircle'
+import {IS_PROD_SERVICE} from 'lib/constants'
+import {useSession} from 'state/session'
export function StepTopicalFeeds() {
const {_} = useLingui()
const {track} = useAnalytics()
+ const {currentAccount} = useSession()
const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
const [selectedFeedUris, setSelectedFeedUris] = React.useState([])
const [saving, setSaving] = React.useState(false)
const suggestedFeedUris = React.useMemo(() => {
- if (!IS_PROD) return []
+ if (!IS_PROD_SERVICE(currentAccount?.service)) return []
return aggregateInterestItems(
state.interestsStepResults.selectedInterests,
state.interestsStepResults.apiResponse.suggestedFeedUris,
state.interestsStepResults.apiResponse.suggestedFeedUris.default,
).slice(0, 10)
- }, [state.interestsStepResults])
+ }, [
+ currentAccount?.service,
+ state.interestsStepResults.apiResponse.suggestedFeedUris,
+ state.interestsStepResults.selectedInterests,
+ ])
const interestsText = React.useMemo(() => {
const i = state.interestsStepResults.selectedInterests.map(
diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts
index b91db92373..405d054d44 100644
--- a/src/state/queries/notifications/feed.ts
+++ b/src/state/queries/notifications/feed.ts
@@ -133,23 +133,6 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
return query
}
-/**
- * This helper is used by the post-thread placeholder function to
- * find a post in the query-data cache
- */
-export function findPostInQueryData(
- queryClient: QueryClient,
- uri: string,
-): AppBskyFeedDefs.PostView | undefined {
- const generator = findAllPostsInQueryData(queryClient, uri)
- const result = generator.next()
- if (result.done) {
- return undefined
- } else {
- return result.value
- }
-}
-
export function* findAllPostsInQueryData(
queryClient: QueryClient,
uri: string,
diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts
index 3200090897..40399395a9 100644
--- a/src/state/queries/post-feed.ts
+++ b/src/state/queries/post-feed.ts
@@ -365,23 +365,6 @@ function createApi(
}
}
-/**
- * This helper is used by the post-thread placeholder function to
- * find a post in the query-data cache
- */
-export function findPostInQueryData(
- queryClient: QueryClient,
- uri: string,
-): AppBskyFeedDefs.PostView | undefined {
- const generator = findAllPostsInQueryData(queryClient, uri)
- const result = generator.next()
- if (result.done) {
- return undefined
- } else {
- return result.value
- }
-}
-
export function* findAllPostsInQueryData(
queryClient: QueryClient,
uri: string,
diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts
index ba42431639..26d40599c6 100644
--- a/src/state/queries/post-thread.ts
+++ b/src/state/queries/post-thread.ts
@@ -8,8 +8,8 @@ import {useQuery, useQueryClient, QueryClient} from '@tanstack/react-query'
import {getAgent} from '#/state/session'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
-import {findPostInQueryData as findPostInFeedQueryData} from './post-feed'
-import {findPostInQueryData as findPostInNotifsQueryData} from './notifications/feed'
+import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from './post-feed'
+import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from './notifications/feed'
import {precacheThreadPostProfiles} from './profile'
import {getEmbeddedPost} from './util'
@@ -82,21 +82,9 @@ export function usePostThreadQuery(uri: string | undefined) {
return undefined
}
{
- const item = findPostInQueryData(queryClient, uri)
- if (item) {
- return threadNodeToPlaceholderThread(item)
- }
- }
- {
- const item = findPostInFeedQueryData(queryClient, uri)
- if (item) {
- return postViewToPlaceholderThread(item)
- }
- }
- {
- const item = findPostInNotifsQueryData(queryClient, uri)
- if (item) {
- return postViewToPlaceholderThread(item)
+ const post = findPostInQueryData(queryClient, uri)
+ if (post) {
+ return post
}
}
return undefined
@@ -171,11 +159,18 @@ function responseToThreadNodes(
AppBskyFeedPost.isRecord(node.post.record) &&
AppBskyFeedPost.validateRecord(node.post.record).success
) {
+ const post = node.post
+ // These should normally be present. They're missing only for
+ // posts that were *just* created. Ideally, the backend would
+ // know to return zeros. Fill them in manually to compensate.
+ post.replyCount ??= 0
+ post.likeCount ??= 0
+ post.repostCount ??= 0
return {
type: 'post',
_reactKey: node.post.uri,
uri: node.post.uri,
- post: node.post,
+ post: post,
record: node.post.record,
parent:
node.parent && direction !== 'down'
@@ -213,14 +208,24 @@ function responseToThreadNodes(
function findPostInQueryData(
queryClient: QueryClient,
uri: string,
-): ThreadNode | undefined {
- const generator = findAllPostsInQueryData(queryClient, uri)
- const result = generator.next()
- if (result.done) {
- return undefined
- } else {
- return result.value
+): ThreadNode | void {
+ let partial
+ for (let item of findAllPostsInQueryData(queryClient, uri)) {
+ if (item.type === 'post') {
+ // Currently, the backend doesn't send full post info in some cases
+ // (for example, for quoted posts). We use missing `likeCount`
+ // as a way to detect that. In the future, we should fix this on
+ // the backend, which will let us always stop on the first result.
+ const hasAllInfo = item.post.likeCount != null
+ if (hasAllInfo) {
+ return item
+ } else {
+ partial = item
+ // Keep searching, we might still find a full post in the cache.
+ }
+ }
}
+ return partial
}
export function* findAllPostsInQueryData(
@@ -236,7 +241,10 @@ export function* findAllPostsInQueryData(
}
for (const item of traverseThread(queryData)) {
if (item.uri === uri) {
- yield item
+ const placeholder = threadNodeToPlaceholderThread(item)
+ if (placeholder) {
+ yield placeholder
+ }
}
const quotedPost =
item.type === 'post' ? getEmbeddedPost(item.post.embed) : undefined
@@ -245,6 +253,12 @@ export function* findAllPostsInQueryData(
}
}
}
+ for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
+ yield postViewToPlaceholderThread(post)
+ }
+ for (let post of findAllPostsInNotifsQueryData(queryClient, uri)) {
+ yield postViewToPlaceholderThread(post)
+ }
}
function* traverseThread(node: ThreadNode): Generator {
diff --git a/src/state/queries/util.ts b/src/state/queries/util.ts
index f3a87ae5db..54752b332a 100644
--- a/src/state/queries/util.ts
+++ b/src/state/queries/util.ts
@@ -53,5 +53,6 @@ export function embedViewRecordToPostView(
record: v.value,
indexedAt: v.indexedAt,
labels: v.labels,
+ embed: v.embeds?.[0],
}
}
diff --git a/src/view/com/auth/create/state.ts b/src/view/com/auth/create/state.ts
index 276eaf924c..68cfaceec5 100644
--- a/src/view/com/auth/create/state.ts
+++ b/src/view/com/auth/create/state.ts
@@ -12,7 +12,7 @@ import {createFullHandle} from '#/lib/strings/handles'
import {cleanError} from '#/lib/strings/errors'
import {useOnboardingDispatch} from '#/state/shell/onboarding'
import {useSessionApi} from '#/state/session'
-import {DEFAULT_SERVICE, IS_PROD} from '#/lib/constants'
+import {DEFAULT_SERVICE, IS_PROD_SERVICE} from '#/lib/constants'
import {
DEFAULT_PROD_FEEDS,
usePreferencesSetBirthDateMutation,
@@ -147,7 +147,7 @@ export function useSubmitCreateAccount(
: undefined,
})
setBirthDate({birthDate: uiState.birthDate})
- if (IS_PROD(uiState.serviceUrl)) {
+ if (IS_PROD_SERVICE(uiState.serviceUrl)) {
setSavedFeeds(DEFAULT_PROD_FEEDS)
}
} catch (e: any) {
diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx
index a706219739..32b5a3141a 100644
--- a/src/view/com/auth/server-input/index.tsx
+++ b/src/view/com/auth/server-input/index.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import {View} from 'react-native'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
-import {PROD_SERVICE} from 'lib/constants'
+import {BSKY_SERVICE} from 'lib/constants'
import * as persisted from '#/state/persisted'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
@@ -26,7 +26,7 @@ export function ServerInputDialog({
const [pdsAddressHistory, setPdsAddressHistory] = React.useState(
persisted.get('pdsAddressHistory') || [],
)
- const [fixedOption, setFixedOption] = React.useState([PROD_SERVICE])
+ const [fixedOption, setFixedOption] = React.useState([BSKY_SERVICE])
const [customAddress, setCustomAddress] = React.useState('')
const onClose = React.useCallback(() => {
@@ -86,7 +86,7 @@ export function ServerInputDialog({
label="Preferences"
values={fixedOption}
onChange={setFixedOption}>
-
+
{_(msg`Bluesky`)}
()
@@ -266,8 +268,8 @@ export function FeedSourceCardLoaded({
{showDescription && feed.description ? (
) : null}
diff --git a/src/view/com/lists/ListCard.tsx b/src/view/com/lists/ListCard.tsx
index 5750faec19..19842eb54d 100644
--- a/src/view/com/lists/ListCard.tsx
+++ b/src/view/com/lists/ListCard.tsx
@@ -3,7 +3,7 @@ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {AtUri, AppBskyGraphDefs, RichText} from '@atproto/api'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
-import {RichText as RichTextCom} from '../util/text/RichText'
+import {RichText as RichTextCom} from '#/components/RichText'
import {UserAvatar} from '../util/UserAvatar'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
@@ -12,6 +12,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links'
import {Trans} from '@lingui/macro'
+import {atoms as a} from '#/alf'
export const ListCard = ({
testID,
@@ -119,9 +120,9 @@ export const ListCard = ({
{descriptionRichText ? (
) : undefined}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index f9bdaebedf..2ef1b1447b 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -11,7 +11,7 @@ import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn'
import {Link, TextLink} from '../util/Link'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
import {Text} from '../util/text/Text'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {s} from 'lib/styles'
@@ -44,6 +44,7 @@ import {ThreadPost} from '#/state/queries/post-thread'
import {useSession} from 'state/session'
import {WhoCanReply} from '../threadgate/WhoCanReply'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
+import {atoms as a} from '#/alf'
export function PostThreadItem({
post,
@@ -326,10 +327,8 @@ let PostThreadItemLoaded = ({
styles.postTextLargeContainer,
]}>
@@ -522,10 +521,8 @@ let PostThreadItemLoaded = ({
{richText?.text ? (
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 2f1c0d37b3..aec916adbe 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -17,7 +17,7 @@ import {PostCtrls} from '../util/post-ctrls/PostCtrls'
import {ContentHider} from '../util/moderation/ContentHider'
import {PostAlerts} from '../util/moderation/PostAlerts'
import {Text} from '../util/text/Text'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
@@ -29,6 +29,7 @@ import {useComposerControls} from '#/state/shell/composer'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {atoms as a} from '#/alf'
export function Post({
post,
@@ -184,11 +185,9 @@ function PostInner({
) : undefined}
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 8d0f2bef26..6f64de181c 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -20,7 +20,7 @@ import {PostCtrls} from '../util/post-ctrls/PostCtrls'
import {PostEmbeds} from '../util/post-embeds'
import {ContentHider} from '../util/moderation/ContentHider'
import {PostAlerts} from '../util/moderation/PostAlerts'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
@@ -36,6 +36,7 @@ import {FeedNameText} from '../util/FeedInfoText'
import {useSession} from '#/state/session'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {atoms as a} from '#/alf'
export function FeedItem({
post,
@@ -347,11 +348,9 @@ let PostContent = ({
) : undefined}
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 8fd50fad6c..3e479d7b53 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -23,7 +23,7 @@ import * as Toast from '../util/Toast'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {Text} from '../util/text/Text'
import {ThemedText} from '../util/text/ThemedText'
-import {RichText} from '../util/text/RichText'
+import {RichText} from '#/components/RichText'
import {UserAvatar} from '../util/UserAvatar'
import {UserBanner} from '../util/UserBanner'
import {ProfileHeaderAlerts} from '../util/moderation/ProfileHeaderAlerts'
@@ -56,6 +56,7 @@ import {Shadow} from '#/state/cache/types'
import {useRequireAuth} from '#/state/session'
import {LabelInfo} from '../util/moderation/LabelInfo'
import {useProfileShadow} from 'state/cache/profile-shadow'
+import {atoms as a} from '#/alf'
let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
const pal = usePalette('default')
@@ -608,12 +609,12 @@ let ProfileHeader = ({
{descriptionRT && !moderation.profile.blur ? (
-
+
) : undefined}
diff --git a/src/view/com/util/EventStopper.tsx b/src/view/com/util/EventStopper.tsx
index 1e672e9457..e743e89bbe 100644
--- a/src/view/com/util/EventStopper.tsx
+++ b/src/view/com/util/EventStopper.tsx
@@ -1,11 +1,14 @@
import React from 'react'
-import {View} from 'react-native'
+import {View, ViewStyle} from 'react-native'
/**
* This utility function captures events and stops
* them from propagating upwards.
*/
-export function EventStopper({children}: React.PropsWithChildren<{}>) {
+export function EventStopper({
+ children,
+ style,
+}: React.PropsWithChildren<{style?: ViewStyle | ViewStyle[]}>) {
const stop = (e: any) => {
e.stopPropagation()
}
@@ -15,7 +18,8 @@ export function EventStopper({children}: React.PropsWithChildren<{}>) {
onTouchEnd={stop}
// @ts-ignore web only -prf
onClick={stop}
- onKeyDown={stop}>
+ onKeyDown={stop}
+ style={style}>
{children}
)
diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
index d556e7669d..cf2db5b333 100644
--- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
@@ -21,7 +21,7 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {AppBskyEmbedExternal} from '@atproto/api'
-import {EmbedPlayerParams, getPlayerHeight} from 'lib/strings/embed-player'
+import {EmbedPlayerParams, getPlayerAspect} from 'lib/strings/embed-player'
import {EventStopper} from '../EventStopper'
import {isNative} from 'platform/detection'
import {NavigationProp} from 'lib/routes/types'
@@ -67,14 +67,12 @@ function PlaceholderOverlay({
// This renders the webview/youtube player as a separate layer
function Player({
- height,
params,
onLoad,
isPlayerActive,
}: {
isPlayerActive: boolean
params: EmbedPlayerParams
- height: number
onLoad: () => void
}) {
// ensures we only load what's requested
@@ -91,25 +89,21 @@ function Player({
if (!isPlayerActive) return null
return (
-
-
-
-
-
-
-
+
+
+
)
}
@@ -129,13 +123,16 @@ export function ExternalPlayer({
const [isPlayerActive, setPlayerActive] = React.useState(false)
const [isLoading, setIsLoading] = React.useState(true)
- const [dim, setDim] = React.useState({
- width: 0,
- height: 0,
- })
+
+ const aspect = React.useMemo(() => {
+ return getPlayerAspect({
+ type: params.type,
+ width: windowDims.width,
+ hasThumb: !!link.thumb,
+ })
+ }, [params.type, windowDims.width, link.thumb])
const viewRef = useAnimatedRef()
-
const frameCallback = useFrameCallback(() => {
const measurement = measure(viewRef)
if (!measurement) return
@@ -180,17 +177,6 @@ export function ExternalPlayer({
}
}, [navigation, isPlayerActive, frameCallback])
- // calculate height for the player and the screen size
- const height = React.useMemo(
- () =>
- getPlayerHeight({
- type: params.type,
- width: dim.width,
- hasThumb: !!link.thumb,
- }),
- [params.type, dim.width, link.thumb],
- )
-
const onLoad = React.useCallback(() => {
setIsLoading(false)
}, [])
@@ -216,32 +202,11 @@ export function ExternalPlayer({
[externalEmbedsPrefs, openModal, params.source],
)
- // measure the layout to set sizing
- const onLayout = React.useCallback(
- (event: {nativeEvent: {layout: {width: any; height: any}}}) => {
- setDim({
- width: event.nativeEvent.layout.width,
- height: event.nativeEvent.layout.height,
- })
- },
- [],
- )
-
return (
-
+
{link.thumb && (!isPlayerActive || isLoading) && (
@@ -251,12 +216,7 @@ export function ExternalPlayer({
isPlayerActive={isPlayerActive}
onPress={onPlayPress}
/>
-
+
)
}
diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx
index d9d84feb45..c128a6f001 100644
--- a/src/view/com/util/post-embeds/QuoteEmbed.tsx
+++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx
@@ -20,7 +20,8 @@ import {PostAlerts} from '../moderation/PostAlerts'
import {makeProfileLink} from 'lib/routes/links'
import {InfoCircleIcon} from 'lib/icons'
import {Trans} from '@lingui/macro'
-import {RichText} from 'view/com/util/text/RichText'
+import {RichText} from '#/components/RichText'
+import {atoms as a} from '#/alf'
export function MaybeQuoteEmbed({
embed,
@@ -127,11 +128,10 @@ export function QuoteEmbed({
) : null}
{richText ? (
) : null}
{embed && }
diff --git a/src/view/com/util/text/RichText.tsx b/src/view/com/util/text/RichText.tsx
index e910127fec..b6d461224c 100644
--- a/src/view/com/util/text/RichText.tsx
+++ b/src/view/com/util/text/RichText.tsx
@@ -10,6 +10,9 @@ import {usePalette} from 'lib/hooks/usePalette'
const WORD_WRAP = {wordWrap: 1}
+/**
+ * @deprecated use `#/components/RichText`
+ */
export function RichText({
testID,
type = 'md',
diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx
index be9eec8165..212c10e745 100644
--- a/src/view/screens/ProfileFeed.tsx
+++ b/src/view/screens/ProfileFeed.tsx
@@ -17,7 +17,7 @@ import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text'
-import {RichText} from 'view/com/util/text/RichText'
+import {RichText} from '#/components/RichText'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {FAB} from 'view/com/util/fab/FAB'
import {EmptyState} from 'view/com/util/EmptyState'
@@ -59,6 +59,7 @@ import {useComposerControls} from '#/state/shell/composer'
import {truncateAndInvalidate} from '#/state/queries/util'
import {isNative} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
+import {atoms as a} from '#/alf'
const SECTION_TITLES = ['Posts', 'About']
@@ -575,9 +576,8 @@ function AboutSection({
{feedInfo.description ? (
) : (
diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx
index 7964648836..d86b569e25 100644
--- a/src/view/screens/ProfileList.tsx
+++ b/src/view/screens/ProfileList.tsx
@@ -14,7 +14,7 @@ import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
import {CenteredView} from 'view/com/util/Views'
import {EmptyState} from 'view/com/util/EmptyState'
import {LoadingScreen} from 'view/com/util/LoadingScreen'
-import {RichText} from 'view/com/util/text/RichText'
+import {RichText} from '#/components/RichText'
import {Button} from 'view/com/util/forms/Button'
import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
@@ -60,6 +60,7 @@ import {
import {logger} from '#/logger'
import {useAnalytics} from '#/lib/analytics/analytics'
import {listenSoftReset} from '#/state/events'
+import {atoms as a} from '#/alf'
const SECTION_TITLES_CURATE = ['Posts', 'About']
const SECTION_TITLES_MOD = ['About']
@@ -742,9 +743,8 @@ const AboutSection = React.forwardRef(
{descriptionRT ? (
) : (
this blogpost.
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index db568c6bd5..09be124db5 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -9,7 +9,8 @@ import * as Prompt from '#/components/Prompt'
import {useDialogStateControlContext} from '#/state/dialogs'
export function Dialogs() {
- const control = Dialog.useDialogControl()
+ const scrollable = Dialog.useDialogControl()
+ const basic = Dialog.useDialogControl()
const prompt = Prompt.usePromptControl()
const {closeAllDialogs} = useDialogStateControlContext()
@@ -20,8 +21,31 @@ export function Dialogs() {
color="secondary"
size="small"
onPress={() => {
- control.open()
+ scrollable.open()
prompt.open()
+ basic.open()
+ }}
+ label="Open basic dialog">
+ Open all dialogs
+
+
+
+
+
diff --git a/src/view/screens/Storybook/Typography.tsx b/src/view/screens/Storybook/Typography.tsx
index 5d3a96f4d0..8ee4270b20 100644
--- a/src/view/screens/Storybook/Typography.tsx
+++ b/src/view/screens/Storybook/Typography.tsx
@@ -8,7 +8,9 @@ import {RichText} from '#/components/RichText'
export function Typography() {
return (
- atoms.text_5xl
+
+ atoms.text_5xl
+
atoms.text_4xl
atoms.text_3xl
atoms.text_2xl
@@ -24,6 +26,7 @@ export function Typography() {
value={`This is rich text. It can have mentions like @bsky.app or links like https://bsky.social`}
/>
+