chore(lint): memoize inline styles in autocomplete, message-input, composer

This commit is contained in:
Mohamed Boudra
2026-04-24 02:34:54 +07:00
parent 4179b24416
commit 032de17537
3 changed files with 69 additions and 32 deletions

View File

@@ -115,7 +115,7 @@ function QueuedMessageRow({ item, onEdit, onSendNow }: QueuedMessageRowProps) {
</Pressable>
<Pressable
onPress={handleSendNow}
style={[styles.queueActionButton, styles.queueSendButton]}
style={QUEUE_SEND_BUTTON_STYLE}
>
<ArrowUp size={theme.iconSize.sm} color="white" />
</Pressable>
@@ -126,10 +126,11 @@ function QueuedMessageRow({ item, onEdit, onSendNow }: QueuedMessageRowProps) {
function ImageAttachmentThumbnail({ image }: { image: ImageAttachment }) {
const uri = useAttachmentPreviewUrl(image);
const source = useMemo(() => ({ uri: uri ?? "" }), [uri]);
if (!uri) {
return <View style={styles.imageThumbnailPlaceholder} />;
}
return <Image source={{ uri }} style={styles.imageThumbnail} />;
return <Image source={source} style={styles.imageThumbnail} />;
}
interface ImageAttachmentPillProps {
@@ -833,6 +834,14 @@ export function Composer({
[],
);
const cancelButtonStyle = useMemo(
() => [
styles.cancelButton as any,
(!isConnected || isCancellingAgent ? styles.buttonDisabled : undefined) as any,
],
[isConnected, isCancellingAgent],
);
const cancelButton = useMemo(
() =>
isAgentRunning && !hasSendableContent && !isProcessing ? (
@@ -842,10 +851,7 @@ export function Composer({
disabled={!isConnected || isCancellingAgent}
accessibilityLabel={isCancellingAgent ? "Canceling agent" : "Stop agent"}
accessibilityRole="button"
style={[
styles.cancelButton as any,
(!isConnected || isCancellingAgent ? styles.buttonDisabled : undefined) as any,
]}
style={cancelButtonStyle}
>
{isCancellingAgent ? (
<ActivityIndicator size="small" color="white" />
@@ -1100,11 +1106,20 @@ export function Composer({
[voiceButtonDisabled],
);
const composerContainerStyle = useMemo(
() => [styles.container, keyboardAnimatedStyle],
[keyboardAnimatedStyle],
);
const inputAreaContainerStyle = useMemo(
() => [styles.inputAreaContainer, isComposerLocked && styles.inputAreaLocked],
[isComposerLocked],
);
return (
<Animated.View style={[styles.container, keyboardAnimatedStyle]}>
<Animated.View style={composerContainerStyle}>
<AttachmentLightbox metadata={lightboxMetadata} onClose={handleLightboxClose} />
{/* Input area */}
<View style={[styles.inputAreaContainer, isComposerLocked && styles.inputAreaLocked]}>
<View style={inputAreaContainerStyle}>
<View style={styles.inputAreaContent}>
{/* Queue list */}
{queuedMessages.length > 0 && (
@@ -1397,3 +1412,5 @@ const styles = StyleSheet.create(((theme: Theme) => ({
fontSize: theme.fontSize.sm,
},
})) as any) as Record<string, any>;
const QUEUE_SEND_BUTTON_STYLE = [styles.queueActionButton, styles.queueSendButton];

View File

@@ -15,6 +15,7 @@ import {
useEffect,
useLayoutEffect,
useImperativeHandle,
useMemo,
forwardRef,
} from "react";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
@@ -981,13 +982,39 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
void handleStopRealtimeVoice();
}, [handleStopRealtimeVoice]);
const inputWrapperCombinedStyle = useMemo(
() => [styles.inputWrapper, inputWrapperStyle, inputAnimatedStyle],
[inputWrapperStyle, inputAnimatedStyle],
);
const textInputStyle = useMemo(
() => [
styles.textInput,
isWeb
? {
height: inputHeight,
minHeight: MIN_INPUT_HEIGHT,
maxHeight: MAX_INPUT_HEIGHT,
}
: {
minHeight: MIN_INPUT_HEIGHT,
maxHeight: MAX_INPUT_HEIGHT,
},
],
[inputHeight],
);
const sendButtonCombinedStyle = useMemo(
() => [styles.sendButton, isSendButtonDisabled && styles.buttonDisabled],
[isSendButtonDisabled],
);
const overlayContainerStyle = useMemo(
() => [styles.overlayContainer, overlayAnimatedStyle],
[overlayAnimatedStyle],
);
return (
<View ref={rootRef} style={styles.container} testID="message-input-root">
{/* Regular input */}
<Animated.View
ref={inputWrapperRef}
style={[styles.inputWrapper, inputWrapperStyle, inputAnimatedStyle]}
>
<Animated.View ref={inputWrapperRef} style={inputWrapperCombinedStyle}>
{/* Text input */}
<View style={styles.textInputScrollWrapper}>
<TextInput
@@ -999,19 +1026,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
accessibilityLabel="Message agent..."
onFocus={handleInputFocus}
onBlur={handleInputBlur}
style={[
styles.textInput,
isWeb
? {
height: inputHeight,
minHeight: MIN_INPUT_HEIGHT,
maxHeight: MAX_INPUT_HEIGHT,
}
: {
minHeight: MIN_INPUT_HEIGHT,
maxHeight: MAX_INPUT_HEIGHT,
},
]}
style={textInputStyle}
multiline
scrollEnabled={isWeb ? inputHeight >= MAX_INPUT_HEIGHT : true}
onContentSizeChange={handleContentSizeChange}
@@ -1148,7 +1163,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
disabled={isSendButtonDisabled}
accessibilityLabel={submitAccessibilityLabel}
accessibilityRole="button"
style={[styles.sendButton, isSendButtonDisabled && styles.buttonDisabled]}
style={sendButtonCombinedStyle}
>
{isSubmitLoading ? (
<ActivityIndicator size="small" color="white" />
@@ -1173,7 +1188,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
</Animated.View>
{/* Dictation overlay */}
<Animated.View style={[styles.overlayContainer, overlayAnimatedStyle]}>
<Animated.View style={overlayContainerStyle}>
{showDictationOverlay ? (
<DictationOverlay
volume={dictationVolume}

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef } from "react";
import { useCallback, useEffect, useMemo, useRef } from "react";
import {
ScrollView,
Text,
@@ -206,9 +206,14 @@ export function Autocomplete({
[ensureActiveItemVisible],
);
const containerStyle = useMemo(
() => [styles.container, { maxHeight }],
[maxHeight],
);
if (isLoading) {
return (
<View style={[styles.container, { maxHeight }]}>
<View style={containerStyle}>
<View style={styles.emptyItem}>
<Text style={styles.emptyText}>{loadingText}</Text>
</View>
@@ -218,7 +223,7 @@ export function Autocomplete({
if (errorMessage) {
return (
<View style={[styles.container, { maxHeight }]}>
<View style={containerStyle}>
<View style={styles.emptyItem}>
<Text style={styles.emptyText}>Error: {errorMessage}</Text>
</View>
@@ -228,7 +233,7 @@ export function Autocomplete({
if (options.length === 0) {
return (
<View style={[styles.container, { maxHeight }]}>
<View style={containerStyle}>
<View style={styles.emptyItem}>
<Text style={styles.emptyText}>{emptyText}</Text>
</View>
@@ -253,7 +258,7 @@ export function Autocomplete({
) : null}
</View>
) : null}
<View style={[styles.container, { maxHeight }]}>
<View style={containerStyle}>
<ScrollView
ref={scrollRef}
onLayout={handleScrollViewLayout}