-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement suggestion for edit composer #35226
Changes from 11 commits
99c7092
dee95c9
d40e5cc
0de2686
a1ef1bf
0273240
31bf412
e5eb167
ee43388
4858707
bd0ccb7
5f4c43a
f11b8ed
a7bcde6
be5faa2
c4094f3
eb09849
a663612
0b5ea25
930eb47
ec83df8
5e5cd88
01c63b8
cc1810a
8dcd362
05f316f
07db2ab
d511cb8
c093a99
616c3c7
8caeee3
8c961f3
a2065fd
be61ac7
4ad4341
265ced8
c7defeb
d5d7f24
10cedd0
3ff18ff
d923e85
0e7eb4f
f57ca56
d734b75
b153d07
c95b4aa
cd0f980
b820982
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -4,6 +4,7 @@ import {View} from 'react-native'; | |||||
import useStyleUtils from '@hooks/useStyleUtils'; | ||||||
import useWindowDimensions from '@hooks/useWindowDimensions'; | ||||||
import * as DeviceCapabilities from '@libs/DeviceCapabilities'; | ||||||
import {measureHeightOfSuggestioContainer} from '@libs/SuggestionUtils'; | ||||||
import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; | ||||||
import type {AutoCompleteSuggestionsProps} from './types'; | ||||||
|
||||||
|
@@ -18,11 +19,13 @@ function AutoCompleteSuggestions<TSuggestion>({measureParentContainer = () => {} | |||||
const StyleUtils = useStyleUtils(); | ||||||
const containerRef = React.useRef<HTMLDivElement>(null); | ||||||
const {windowHeight, windowWidth} = useWindowDimensions(); | ||||||
const suggestionContainerHeight = measureHeightOfSuggestioContainer(props.suggestions.length, props.isSuggestionPickerLarge); | ||||||
const [{width, left, bottom}, setContainerState] = React.useState({ | ||||||
width: 0, | ||||||
left: 0, | ||||||
bottom: 0, | ||||||
}); | ||||||
const [shouldBelowContainer, setShouldBelowContainer] = React.useState(false); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
React.useEffect(() => { | ||||||
const container = containerRef.current; | ||||||
if (!container) { | ||||||
|
@@ -41,13 +44,19 @@ function AutoCompleteSuggestions<TSuggestion>({measureParentContainer = () => {} | |||||
if (!measureParentContainer) { | ||||||
return; | ||||||
} | ||||||
measureParentContainer((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w})); | ||||||
}, [measureParentContainer, windowHeight, windowWidth]); | ||||||
|
||||||
measureParentContainer((x, y, w, h) => { | ||||||
const currenBottom = y < suggestionContainerHeight ? windowHeight - y - suggestionContainerHeight - h : windowHeight - y; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
setShouldBelowContainer(y < suggestionContainerHeight); | ||||||
setContainerState({left: x, bottom: currenBottom, width: w}); | ||||||
}); | ||||||
}, [measureParentContainer, windowHeight, windowWidth, suggestionContainerHeight]); | ||||||
|
||||||
const componentToRender = ( | ||||||
<BaseAutoCompleteSuggestions<TSuggestion> | ||||||
// eslint-disable-next-line react/jsx-props-no-spreading | ||||||
{...props} | ||||||
shouldBelowParentContainer={shouldBelowContainer} | ||||||
ref={containerRef} | ||||||
/> | ||||||
); | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -20,4 +20,24 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight | |||||
return availableHeight > menuHeight; | ||||||
} | ||||||
|
||||||
export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu}; | ||||||
const measureHeightOfSuggestioContainer = (numRows: number, isSuggestionPickerLarge: boolean): number => { | ||||||
dukenv0307 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + 2; | ||||||
stitesExpensify marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
let suggestionHeight = 0; | ||||||
|
||||||
if (isSuggestionPickerLarge) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
And all of the occurances below (i.e. suggestionHeight) |
||||||
if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) { | ||||||
// On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available | ||||||
suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; | ||||||
} else { | ||||||
suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; | ||||||
} | ||||||
} else if (numRows > 2) { | ||||||
// On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible | ||||||
suggestionHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; | ||||||
} else { | ||||||
suggestionHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; | ||||||
} | ||||||
return suggestionHeight + borderAndPadding; | ||||||
}; | ||||||
|
||||||
export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestioContainer}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's rename this to SuggestionsActions |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import React from 'react'; | ||
import type {NativeSyntheticEvent, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native'; | ||
|
||
type SuggestionsRef = { | ||
getSuggestions: () => void; | ||
resetSuggestions: () => void; | ||
triggerHotkeyActions: (event: NativeSyntheticEvent<TextInputKeyPressEventData> | KeyboardEvent) => boolean; | ||
onSelectionChange: (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void; | ||
updateShouldShowSuggestionMenuToFalse: () => void; | ||
setShouldBlockSuggestionCalc: () => void; | ||
}; | ||
|
||
const suggestionsRef = React.createRef<SuggestionsRef>(); | ||
|
||
function resetSuggestions() { | ||
if (!suggestionsRef.current) { | ||
return; | ||
} | ||
|
||
suggestionsRef.current.resetSuggestions(); | ||
} | ||
|
||
function triggerHotkeyActions(event: NativeSyntheticEvent<TextInputKeyPressEventData> | KeyboardEvent): boolean { | ||
if (!suggestionsRef.current) { | ||
return false; | ||
} | ||
|
||
return suggestionsRef.current.triggerHotkeyActions(event); | ||
} | ||
|
||
function updateShouldShowSuggestionMenuToFalse() { | ||
if (!suggestionsRef.current) { | ||
return; | ||
} | ||
|
||
suggestionsRef.current.updateShouldShowSuggestionMenuToFalse(); | ||
} | ||
|
||
function onSelectionChange(event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) { | ||
if (!suggestionsRef.current) { | ||
return; | ||
} | ||
|
||
suggestionsRef.current.onSelectionChange(event); | ||
} | ||
|
||
export {suggestionsRef, resetSuggestions, triggerHotkeyActions, onSelectionChange, updateShouldShowSuggestionMenuToFalse}; | ||
|
||
// eslint-disable-next-line import/prefer-default-export | ||
export type {SuggestionsRef}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; | ||
import React, {useState} from 'react'; | ||
import type {TextInput} from 'react-native'; | ||
import Composer from '@components/Composer'; | ||
import type {ComposerProps} from '@components/Composer/types'; | ||
import type {SuggestionsRef} from '@libs/actions/SuggestionsAction'; | ||
import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; | ||
|
||
type ComposerWithSuggestionsEditProps = { | ||
setValue: Dispatch<SetStateAction<string>>; | ||
setSelection: Dispatch< | ||
SetStateAction<{ | ||
start: number; | ||
end: number; | ||
}> | ||
>; | ||
resetKeyboardInput: () => void; | ||
isComposerFocused: boolean; | ||
suggestionsRef: RefObject<SuggestionsRef>; | ||
updateDraft: (newValue: string) => void; | ||
measureParentContainer: (callback: () => void) => void; | ||
}; | ||
|
||
function ComposerWithSuggestionsEdit( | ||
{ | ||
value, | ||
maxLines = -1, | ||
onKeyPress = () => {}, | ||
style, | ||
onSelectionChange = () => {}, | ||
selection = { | ||
start: 0, | ||
end: 0, | ||
}, | ||
onBlur = () => {}, | ||
onFocus = () => {}, | ||
onChangeText = () => {}, | ||
setValue = () => {}, | ||
setSelection = () => {}, | ||
resetKeyboardInput = () => {}, | ||
isComposerFocused, | ||
suggestionsRef, | ||
updateDraft, | ||
measureParentContainer, | ||
id = undefined, | ||
}: ComposerWithSuggestionsEditProps & ComposerProps, | ||
ref: ForwardedRef<TextInput>, | ||
) { | ||
const [composerHeight, setComposerHeight] = useState(0); | ||
|
||
return ( | ||
<> | ||
<Composer | ||
multiline | ||
ref={ref} | ||
id={id} | ||
onChangeText={onChangeText} // Debounced saveDraftComment | ||
onKeyPress={onKeyPress} | ||
value={value} | ||
maxLines={maxLines} // This is the same that slack has | ||
style={style} | ||
onFocus={onFocus} | ||
onBlur={onBlur} | ||
selection={selection} | ||
onSelectionChange={onSelectionChange} | ||
onLayout={(e) => { | ||
const composerLayoutHeight = e.nativeEvent.layout.height; | ||
if (composerHeight === composerLayoutHeight) { | ||
return; | ||
} | ||
setComposerHeight(composerLayoutHeight); | ||
}} | ||
/> | ||
|
||
<Suggestions | ||
ref={suggestionsRef} | ||
// @ts-expect-error TODO: Remove this once Suggestions is migrated to TypeScript. | ||
isComposerFullSize={false} | ||
isComposerFocused={isComposerFocused} | ||
updateComment={updateDraft} | ||
composerHeight={composerHeight} | ||
measureParentContainer={measureParentContainer} | ||
isAutoSuggestionPickerLarge | ||
value={value} | ||
setValue={setValue} | ||
selection={selection} | ||
setSelection={setSelection} | ||
resetKeyboardInput={resetKeyboardInput} | ||
/> | ||
</> | ||
); | ||
} | ||
|
||
export default React.forwardRef(ComposerWithSuggestionsEdit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.