@@ -39,6 +39,9 @@ import useConciergeAttachmentPicker from './useConciergeAttachmentPicker';
3939// Max number of lines before the input starts scrolling internally.
4040const MAX_INPUT_LINES = 5 ;
4141
42+ // A single line of placeholder text is one lineHeightXLarge tall. Anything meaningfully taller has wrapped.
43+ const SINGLE_LINE_PLACEHOLDER_MAX_HEIGHT = variables . lineHeightXLarge * 1.5 ;
44+
4245type ConciergePromptBoxProps = {
4346 /**
4447 * Visibility of the "+" actions menu is owned by HomePage (above the narrow/wide layout branch) so it survives the
@@ -65,6 +68,7 @@ function ConciergePromptBox({isMenuVisible, setIsMenuVisible}: ConciergePromptBo
6568 // shouldCalculateCaretPosition), otherwise every value update re-renders it with the caret at the start.
6669 const [ selection , setSelection ] = useState ( { start : 0 , end : 0 } ) ;
6770 const [ isFocused , setIsFocused ] = useState ( false ) ;
71+ const [ longPlaceholderHeight , setLongPlaceholderHeight ] = useState < number | null > ( null ) ;
6872 const [ popoverAnchorPosition , setPopoverAnchorPosition ] = useState < AnchorPosition | null > ( null ) ;
6973 const actionButtonRef = useRef < View | HTMLDivElement | null > ( null ) ;
7074
@@ -96,7 +100,13 @@ function ConciergePromptBox({isMenuVisible, setIsMenuVisible}: ConciergePromptBo
96100 const localNow = getLocalDateFromDatetime ( ) ;
97101 const dateLabel = DateUtils . formatToLongDateWithWeekdayWithoutYear ( localNow , dateFnsLocale ) ;
98102 const greeting = translate ( `homePage.conciergePrompt.${ DateUtils . getTimeOfDayGreetingKey ( localNow ) } ` , { name : firstName } ) ;
99- const placeholder = translate ( shouldUseNarrowLayout ? 'homePage.conciergePrompt.inputPlaceholderMobile' : 'homePage.conciergePrompt.inputPlaceholder' ) ;
103+ const longPlaceholder = translate ( 'homePage.conciergePrompt.inputPlaceholder' ) ;
104+ const shortPlaceholder = translate ( 'homePage.conciergePrompt.inputPlaceholderMobile' ) ;
105+
106+ // On the wide layout, fall back to the short placeholder when the long one would wrap past one line
107+ // (until it is measured, optimistically assume it fits to avoid a flash of the short copy).
108+ const longPlaceholderFitsOneLine = longPlaceholderHeight === null || longPlaceholderHeight <= SINGLE_LINE_PLACEHOLDER_MAX_HEIGHT ;
109+ const placeholder = shouldUseNarrowLayout || ! longPlaceholderFitsOneLine ? shortPlaceholder : longPlaceholder ;
100110 const canSubmit = shouldShowAskConcierge && value . trim ( ) . length > 0 ;
101111
102112 const submit = ( ) => {
@@ -200,21 +210,36 @@ function ConciergePromptBox({isMenuVisible, setIsMenuVisible}: ConciergePromptBo
200210 </ View >
201211 < View style = { styles . conciergePromptBoxDivider } />
202212 </ View >
203- < Composer
204- style = { [ styles . textNormal , styles . noOutline , styles . flex1 , styles . conciergePromptBoxInput ] }
205- value = { value }
206- onChangeText = { setValue }
207- selection = { selection }
208- onSelectionChange = { ( event ) => setSelection ( event . nativeEvent . selection ) }
209- shouldCalculateCaretPosition
210- onFocus = { ( ) => setIsFocused ( true ) }
211- onBlur = { ( ) => setIsFocused ( false ) }
212- onKeyPress = { handleKeyPress }
213- maxLines = { MAX_INPUT_LINES }
214- multiline
215- placeholder = { placeholder }
216- accessibilityLabel = { placeholder }
217- />
213+ < View style = { [ styles . flex1 , styles . flexRow , styles . pRelative ] } >
214+ < Composer
215+ style = { [ styles . textNormal , styles . noOutline , styles . flex1 , styles . conciergePromptBoxInput ] }
216+ value = { value }
217+ onChangeText = { setValue }
218+ selection = { selection }
219+ onSelectionChange = { ( event ) => setSelection ( event . nativeEvent . selection ) }
220+ shouldCalculateCaretPosition
221+ onFocus = { ( ) => setIsFocused ( true ) }
222+ onBlur = { ( ) => setIsFocused ( false ) }
223+ onKeyPress = { handleKeyPress }
224+ maxLines = { MAX_INPUT_LINES }
225+ multiline
226+ placeholder = { placeholder }
227+ accessibilityLabel = { placeholder }
228+ />
229+ { /* Hidden probe stretched to the input's width. Its height reveals whether the long placeholder wraps past one line. */ }
230+ < View
231+ pointerEvents = "none"
232+ style = { [ styles . pAbsolute , styles . t0 , styles . l0 , styles . r0 , styles . opacity0 ] }
233+ onLayout = { ( event ) => setLongPlaceholderHeight ( event . nativeEvent . layout . height ) }
234+ >
235+ < Text
236+ accessible = { false }
237+ style = { [ styles . textNormal , styles . conciergePromptBoxInput ] }
238+ >
239+ { longPlaceholder }
240+ </ Text >
241+ </ View >
242+ </ View >
218243 < Tooltip text = { translate ( 'common.send' ) } >
219244 < PressableWithFeedback
220245 accessibilityLabel = { translate ( 'common.send' ) }
0 commit comments