about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/custom-animations/PressableScale.tsx17
-rw-r--r--src/view/com/composer/text-input/TextInput.tsx6
-rw-r--r--src/view/com/composer/text-input/mobile/Autocomplete.tsx195
-rw-r--r--src/view/shell/bottom-bar/BottomBar.tsx7
4 files changed, 105 insertions, 120 deletions
diff --git a/src/lib/custom-animations/PressableScale.tsx b/src/lib/custom-animations/PressableScale.tsx
index d6eabf8b2..ca080dc8a 100644
--- a/src/lib/custom-animations/PressableScale.tsx
+++ b/src/lib/custom-animations/PressableScale.tsx
@@ -13,17 +13,19 @@ import {isNative} from '#/platform/detection'
 
 const DEFAULT_TARGET_SCALE = isNative || isTouchDevice ? 0.98 : 1
 
+const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
+
 export function PressableScale({
   targetScale = DEFAULT_TARGET_SCALE,
   children,
-  contentContainerStyle,
+  style,
   onPressIn,
   onPressOut,
   ...rest
 }: {
   targetScale?: number
-  contentContainerStyle?: StyleProp<ViewStyle>
-} & Exclude<PressableProps, 'onPressIn' | 'onPressOut'>) {
+  style?: StyleProp<ViewStyle>
+} & Exclude<PressableProps, 'onPressIn' | 'onPressOut' | 'style'>) {
   const scale = useSharedValue(1)
 
   const animatedStyle = useAnimatedStyle(() => ({
@@ -31,7 +33,7 @@ export function PressableScale({
   }))
 
   return (
-    <Pressable
+    <AnimatedPressable
       accessibilityRole="button"
       onPressIn={e => {
         'worklet'
@@ -49,10 +51,9 @@ export function PressableScale({
         cancelAnimation(scale)
         scale.value = withTiming(1, {duration: 100})
       }}
+      style={[animatedStyle, style]}
       {...rest}>
-      <Animated.View style={[animatedStyle, contentContainerStyle]}>
-        {children as React.ReactNode}
-      </Animated.View>
-    </Pressable>
+      {children}
+    </AnimatedPressable>
   )
 }
diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx
index 3df9cfca4..39baa2cb6 100644
--- a/src/view/com/composer/text-input/TextInput.tsx
+++ b/src/view/com/composer/text-input/TextInput.tsx
@@ -245,7 +245,11 @@ export const TextInput = forwardRef(function TextInputImpl(
         multiline
         scrollEnabled={false}
         numberOfLines={4}
-        style={[inputTextStyle, a.w_full, {textAlignVertical: 'top'}]}
+        style={[
+          inputTextStyle,
+          a.w_full,
+          {textAlignVertical: 'top', minHeight: 60},
+        ]}
         {...props}>
         {textDecorated}
       </PasteInput>
diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx
index 9c8f8f916..3d2bcfa61 100644
--- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx
+++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx
@@ -1,13 +1,17 @@
-import React, {useEffect, useRef} from 'react'
-import {Animated, TouchableOpacity, StyleSheet, View} from 'react-native'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
-import {usePalette} from 'lib/hooks/usePalette'
-import {Text} from 'view/com/util/text/Text'
-import {UserAvatar} from 'view/com/util/UserAvatar'
-import {useGrapheme} from '../hooks/useGrapheme'
-import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
-import {Trans} from '@lingui/macro'
+import React, {useRef} from 'react'
+import {View} from 'react-native'
+import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated'
 import {AppBskyActorDefs} from '@atproto/api'
+import {Trans} from '@lingui/macro'
+
+import {PressableScale} from '#/lib/custom-animations/PressableScale'
+import {sanitizeDisplayName} from '#/lib/strings/display-names'
+import {sanitizeHandle} from '#/lib/strings/handles'
+import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+import {useGrapheme} from '../hooks/useGrapheme'
 
 export function Autocomplete({
   prefix,
@@ -16,8 +20,8 @@ export function Autocomplete({
   prefix: string
   onSelect: (item: string) => void
 }) {
-  const pal = usePalette('default')
-  const positionInterp = useAnimatedValue(0)
+  const t = useTheme()
+
   const {getGraphemeString} = useGrapheme()
   const isActive = !!prefix
   const {data: suggestions, isFetching} = useActorAutocompleteQuery(prefix)
@@ -28,108 +32,85 @@ export function Autocomplete({
     suggestionsRef.current = suggestions
   }
 
-  useEffect(() => {
-    Animated.timing(positionInterp, {
-      toValue: isActive ? 1 : 0,
-      duration: 200,
-      useNativeDriver: true,
-    }).start()
-  }, [positionInterp, isActive])
-
-  const topAnimStyle = {
-    transform: [
-      {
-        translateY: positionInterp.interpolate({
-          inputRange: [0, 1],
-          outputRange: [200, 0],
-        }),
-      },
-    ],
-  }
+  if (!isActive) return null
 
   return (
-    <Animated.View style={topAnimStyle}>
-      {isActive ? (
-        <View style={[pal.view, styles.container, pal.border]}>
-          {suggestionsRef.current?.length ? (
-            suggestionsRef.current.slice(0, 5).map(item => {
-              // Eventually use an average length
-              const MAX_CHARS = 40
-              const MAX_HANDLE_CHARS = 20
+    <Animated.View
+      entering={FadeInDown.duration(200)}
+      exiting={FadeOut.duration(100)}
+      style={[
+        t.atoms.bg,
+        a.mt_sm,
+        a.border,
+        a.rounded_sm,
+        t.atoms.border_contrast_high,
+        {marginLeft: -62},
+      ]}>
+      {suggestionsRef.current?.length ? (
+        suggestionsRef.current.slice(0, 5).map((item, index, arr) => {
+          // Eventually use an average length
+          const MAX_CHARS = 40
+          const MAX_HANDLE_CHARS = 20
 
-              // Using this approach because styling is not respecting
-              // bounding box wrapping (before converting to ellipsis)
-              const {name: displayHandle, remainingCharacters} =
-                getGraphemeString(item.handle, MAX_HANDLE_CHARS)
+          // Using this approach because styling is not respecting
+          // bounding box wrapping (before converting to ellipsis)
+          const {name: displayHandle, remainingCharacters} = getGraphemeString(
+            item.handle,
+            MAX_HANDLE_CHARS,
+          )
 
-              const {name: displayName} = getGraphemeString(
-                item.displayName ?? item.handle,
-                MAX_CHARS -
-                  MAX_HANDLE_CHARS +
-                  (remainingCharacters > 0 ? remainingCharacters : 0),
-              )
+          const {name: displayName} = getGraphemeString(
+            item.displayName || item.handle,
+            MAX_CHARS -
+              MAX_HANDLE_CHARS +
+              (remainingCharacters > 0 ? remainingCharacters : 0),
+          )
 
-              return (
-                <TouchableOpacity
-                  testID="autocompleteButton"
-                  key={item.handle}
-                  style={[pal.border, styles.item]}
-                  onPress={() => onSelect(item.handle)}
-                  accessibilityLabel={`Select ${item.handle}`}
-                  accessibilityHint="">
-                  <View style={styles.avatarAndHandle}>
-                    <UserAvatar
-                      avatar={item.avatar ?? null}
-                      size={24}
-                      type={item.associated?.labeler ? 'labeler' : 'user'}
-                    />
-                    <Text type="md-medium" style={pal.text}>
-                      {displayName}
-                    </Text>
-                  </View>
-                  <Text type="sm" style={pal.textLight} numberOfLines={1}>
-                    @{displayHandle}
+          return (
+            <View
+              style={[
+                index !== arr.length - 1 && a.border_b,
+                t.atoms.border_contrast_high,
+                a.px_sm,
+                a.py_md,
+              ]}
+              key={item.handle}>
+              <PressableScale
+                testID="autocompleteButton"
+                style={[
+                  a.flex_row,
+                  a.gap_sm,
+                  a.justify_between,
+                  a.align_center,
+                ]}
+                onPress={() => onSelect(item.handle)}
+                accessibilityLabel={`Select ${item.handle}`}
+                accessibilityHint="">
+                <View style={[a.flex_row, a.gap_sm, a.align_center]}>
+                  <UserAvatar
+                    avatar={item.avatar ?? null}
+                    size={24}
+                    type={item.associated?.labeler ? 'labeler' : 'user'}
+                  />
+                  <Text
+                    style={[a.text_md, a.font_bold]}
+                    emoji={true}
+                    numberOfLines={1}>
+                    {sanitizeDisplayName(displayName)}
                   </Text>
-                </TouchableOpacity>
-              )
-            })
-          ) : (
-            <Text type="sm" style={[pal.text, pal.border, styles.noResults]}>
-              {isFetching ? (
-                <Trans>Loading...</Trans>
-              ) : (
-                <Trans>No result</Trans>
-              )}
-            </Text>
-          )}
-        </View>
-      ) : null}
+                </View>
+                <Text style={[t.atoms.text_contrast_medium]} numberOfLines={1}>
+                  {sanitizeHandle(displayHandle, '@')}
+                </Text>
+              </PressableScale>
+            </View>
+          )
+        })
+      ) : (
+        <Text style={[a.text_md, a.px_sm, a.py_md]}>
+          {isFetching ? <Trans>Loading...</Trans> : <Trans>No result</Trans>}
+        </Text>
+      )}
     </Animated.View>
   )
 }
-
-const styles = StyleSheet.create({
-  container: {
-    marginLeft: -50, // Composer avatar width
-    top: 10,
-    borderTopWidth: 1,
-  },
-  item: {
-    borderBottomWidth: 1,
-    paddingVertical: 12,
-    display: 'flex',
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'space-between',
-    gap: 6,
-  },
-  avatarAndHandle: {
-    display: 'flex',
-    flexDirection: 'row',
-    gap: 6,
-    alignItems: 'center',
-  },
-  noResults: {
-    paddingVertical: 12,
-  },
-})
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index 9187b5321..af06134fc 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -351,17 +351,16 @@ function Btn({
   return (
     <PressableScale
       testID={testID}
-      style={styles.ctrl}
+      style={[styles.ctrl, a.flex_1]}
       onPress={onPress}
       onLongPress={onLongPress}
       accessible={accessible}
       accessibilityLabel={accessibilityLabel}
       accessibilityHint={accessibilityHint}
-      targetScale={0.8}
-      contentContainerStyle={[a.flex_1]}>
+      targetScale={0.8}>
       {icon}
       {notificationCount ? (
-        <View style={[styles.notificationCount, {top: -5}]}>
+        <View style={[styles.notificationCount]}>
           <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
         </View>
       ) : undefined}