Skip to content

Commit

Permalink
Merge pull request #25564 from software-mansion-labs/simplify-global-…
Browse files Browse the repository at this point in the history
…create-menu
  • Loading branch information
thienlnam authored Sep 15, 2023
2 parents 351367a + 4c478d3 commit 446f29b
Show file tree
Hide file tree
Showing 31 changed files with 604 additions and 627 deletions.
7 changes: 5 additions & 2 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@ const CONST = {
},
type: KEYBOARD_SHORTCUT_NAVIGATION_TYPE,
},
NEW_GROUP: {
descriptionKey: 'newGroup',
NEW_CHAT: {
descriptionKey: 'newChat',
shortcutKey: 'K',
modifiers: ['CTRL', 'SHIFT'],
trigger: {
Expand Down Expand Up @@ -2624,6 +2624,9 @@ const CONST = {
DISABLED: 'DISABLED',
},
TAB: {
NEW_CHAT_TAB_ID: 'NewChatTab',
NEW_CHAT: 'chat',
NEW_ROOM: 'room',
RECEIPT_TAB_ID: 'ReceiptTab',
MANUAL: 'manual',
SCAN: 'scan',
Expand Down
5 changes: 2 additions & 3 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ type ParseReportRouteParams = {

const REPORT = 'r';
const IOU_REQUEST = 'request/new';
const IOU_BILL = 'split/new';
const IOU_SEND = 'send/new';
const NEW_TASK = 'new/task';
const SETTINGS_PERSONAL_DETAILS = 'settings/profile/personal-details';
Expand Down Expand Up @@ -67,8 +66,9 @@ export default {
SETTINGS_2FA: 'settings/security/two-factor-auth',
SETTINGS_STATUS,
SETTINGS_STATUS_SET,
NEW_GROUP: 'new/group',
NEW: 'new',
NEW_CHAT: 'new/chat',
NEW_ROOM: 'new/room',
NEW_TASK,
REPORT,
REPORT_WITH_ID: 'r/:reportID?/:reportActionID?',
Expand All @@ -86,7 +86,6 @@ export default {
CONCIERGE: 'concierge',

IOU_REQUEST,
IOU_BILL,
IOU_SEND,

// To see the available iouType, please refer to CONST.IOU.MONEY_REQUEST_TYPE
Expand Down
35 changes: 34 additions & 1 deletion src/components/OptionRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as StyleUtils from '../styles/StyleUtils';
import optionPropTypes from './optionPropTypes';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Button from './Button';
import MultipleAvatars from './MultipleAvatars';
import Hoverable from './Hoverable';
import DisplayNames from './DisplayNames';
Expand Down Expand Up @@ -39,6 +40,15 @@ const propTypes = {
/** Whether we should show the selected state */
showSelectedState: PropTypes.bool,

/** Whether to show a button pill instead of a tickbox */
shouldShowSelectedStateAsButton: PropTypes.bool,

/** Text for button pill */
selectedStateButtonText: PropTypes.string,

/** Callback to fire when the multiple selector (tickbox or button) is clicked */
onSelectedStatePressed: PropTypes.func,

/** Whether we highlight selected option */
highlightSelected: PropTypes.bool,

Expand Down Expand Up @@ -71,6 +81,9 @@ const propTypes = {
const defaultProps = {
hoverStyle: styles.sidebarLinkHover,
showSelectedState: false,
shouldShowSelectedStateAsButton: false,
selectedStateButtonText: 'Select',
onSelectedStatePressed: () => {},
highlightSelected: false,
isSelected: false,
boldStyle: false,
Expand Down Expand Up @@ -100,6 +113,7 @@ class OptionRow extends Component {
this.props.isMultilineSupported !== nextProps.isMultilineSupported ||
this.props.isSelected !== nextProps.isSelected ||
this.props.shouldHaveOptionSeparator !== nextProps.shouldHaveOptionSeparator ||
this.props.selectedStateButtonText !== nextProps.selectedStateButtonText ||
this.props.showSelectedState !== nextProps.showSelectedState ||
this.props.highlightSelected !== nextProps.highlightSelected ||
this.props.showTitleTooltip !== nextProps.showTitleTooltip ||
Expand Down Expand Up @@ -259,7 +273,26 @@ class OptionRow extends Component {
/>
</View>
)}
{this.props.showSelectedState && <SelectCircle isChecked={this.props.isSelected} />}
{this.props.showSelectedState && (
<>
{this.props.shouldShowSelectedStateAsButton && !this.props.isSelected ? (
<Button
style={[styles.pl2]}
text={this.props.selectedStateButtonText}
onPress={() => this.props.onSelectedStatePressed(this.props.option)}
small
/>
) : (
<PressableWithFeedback
onPress={() => this.props.onSelectedStatePressed(this.props.option)}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.CHECKBOX}
accessibilityLabel={CONST.ACCESSIBILITY_ROLE.CHECKBOX}
>
<SelectCircle isChecked={this.props.isSelected} />
</PressableWithFeedback>
)}
</>
)}
{this.props.isSelected && this.props.highlightSelected && (
<View style={styles.defaultCheckmarkWrapper}>
<Icon
Expand Down
6 changes: 6 additions & 0 deletions src/components/OptionsList/BaseOptionsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ function BaseOptionsList({
shouldDisableRowInnerPadding,
disableFocusOptions,
canSelectMultipleOptions,
shouldShowMultipleOptionSelectorAsButton,
multipleOptionSelectorButtonText,
onAddToSelection,
highlightSelectedOptions,
onSelectRow,
boldStyle,
Expand Down Expand Up @@ -191,6 +194,9 @@ function BaseOptionsList({
onSelectRow={onSelectRow}
isSelected={isSelected}
showSelectedState={canSelectMultipleOptions}
shouldShowSelectedStateAsButton={shouldShowMultipleOptionSelectorAsButton}
selectedStateButtonText={multipleOptionSelectorButtonText}
onSelectedStatePressed={onAddToSelection}
highlightSelected={highlightSelectedOptions}
boldStyle={boldStyle}
isDisabled={isItemDisabled}
Expand Down
28 changes: 17 additions & 11 deletions src/components/OptionsSelector/BaseOptionsSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _ from 'underscore';
import lodashGet from 'lodash/get';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {View, InteractionManager} from 'react-native';
import {View} from 'react-native';
import Button from '../Button';
import FixedFooter from '../FixedFooter';
import OptionsList from '../OptionsList';
Expand All @@ -16,11 +16,9 @@ import KeyboardShortcut from '../../libs/KeyboardShortcut';
import {propTypes as optionsSelectorPropTypes, defaultProps as optionsSelectorDefaultProps} from './optionsSelectorPropTypes';
import setSelection from '../../libs/setSelection';
import compose from '../../libs/compose';
import getPlatform from '../../libs/getPlatform';

const propTypes = {
/** Whether we should wait before focusing the TextInput, useful when using transitions on Android */
shouldDelayFocus: PropTypes.bool,

/** padding bottom style of safe area */
safeAreaPaddingBottomStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),

Expand Down Expand Up @@ -70,6 +68,12 @@ class BaseOptionsSelector extends Component {
componentDidMount() {
this.subscribeToKeyboardShortcut();

if (this.props.isFocused && this.props.autoFocus && this.textInput) {
setTimeout(() => {
this.textInput.focus();
}, CONST.ANIMATED_TRANSITION);
}

this.scrollToIndex(this.props.selectedOptions.length ? 0 : this.state.focusedIndex, false);
}

Expand All @@ -82,12 +86,13 @@ class BaseOptionsSelector extends Component {
}
}

if (this.textInput && this.props.autoFocus && !prevProps.isFocused && this.props.isFocused) {
InteractionManager.runAfterInteractions(() => {
// If we automatically focus on a text input when mounting a component,
// let's automatically focus on it when the component updates as well (eg, when navigating back from a page)
// Screen coming back into focus, for example
// when doing Cmd+Shift+K, then Cmd+K, then Cmd+Shift+K.
// Only applies to platforms that support keyboard shortcuts
if ([CONST.PLATFORM.DESKTOP, CONST.PLATFORM.WEB].includes(getPlatform()) && !prevProps.isFocused && this.props.isFocused && this.props.autoFocus && this.textInput) {
setTimeout(() => {
this.textInput.focus();
});
}, CONST.ANIMATED_TRANSITION);
}

if (_.isEqual(this.props.sections, prevProps.sections)) {
Expand Down Expand Up @@ -359,8 +364,6 @@ class BaseOptionsSelector extends Component {
selectTextOnFocus
blurOnSubmit={Boolean(this.state.allOptions.length)}
spellCheck={false}
autoFocus={this.props.autoFocus}
shouldDelayFocus={this.props.shouldDelayFocus}
/>
);
const optionsList = (
Expand All @@ -372,6 +375,9 @@ class BaseOptionsSelector extends Component {
focusedIndex={this.state.focusedIndex}
selectedOptions={this.props.selectedOptions}
canSelectMultipleOptions={this.props.canSelectMultipleOptions}
shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton}
multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText}
onAddToSelection={this.props.onAddToSelection}
hideSectionHeaders={this.props.hideSectionHeaders}
headerMessage={this.props.headerMessage}
boldStyle={this.props.boldStyle}
Expand Down
12 changes: 12 additions & 0 deletions src/components/OptionsSelector/optionsSelectorPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ const propTypes = {
/** Whether we can select multiple options */
canSelectMultipleOptions: PropTypes.bool,

/** Whether to show a button pill instead of a standard tickbox */
shouldShowMultipleOptionSelectorAsButton: PropTypes.bool,

/** Text for button pill */
multipleOptionSelectorButtonText: PropTypes.string,

/** Callback to fire when the multiple selector (tickbox or button) is clicked */
onAddToSelection: PropTypes.func,

/** Whether we highlight selected options */
highlightSelectedOptions: PropTypes.bool,

Expand Down Expand Up @@ -125,6 +134,9 @@ const defaultProps = {
selectedOptions: [],
headerMessage: '',
canSelectMultipleOptions: false,
shouldShowMultipleOptionSelectorAsButton: false,
multipleOptionSelectorButtonText: '',
onAddToSelection: () => {},
highlightSelectedOptions: false,
hideSectionHeaders: false,
boldStyle: false,
Expand Down
7 changes: 4 additions & 3 deletions src/components/RoomNameInput/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import useLocalize from '../../hooks/useLocalize';
import * as roomNameInputPropTypes from './roomNameInputPropTypes';
import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils';

function RoomNameInput({autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange}) {
function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) {
const {translate} = useLocalize();

const [selection, setSelection] = useState();
Expand Down Expand Up @@ -57,8 +57,9 @@ function RoomNameInput({autoFocus, disabled, errorText, forwardedRef, value, onB
onSelectionChange={(event) => setSelection(event.nativeEvent.selection)}
errorText={errorText}
autoCapitalize="none"
onBlur={onBlur}
autoFocus={autoFocus}
onBlur={() => isFocused && onBlur()}
shouldDelayFocus={shouldDelayFocus}
autoFocus={isFocused && autoFocus}
maxLength={CONST.REPORT.MAX_ROOM_NAME_LENGTH}
spellCheck={false}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/components/RoomNameInput/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as roomNameInputPropTypes from './roomNameInputPropTypes';
import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils';
import getOperatingSystem from '../../libs/getOperatingSystem';

function RoomNameInput({autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) {
function RoomNameInput({isFocused, autoFocus, disabled, errorText, forwardedRef, value, onBlur, onChangeText, onInputChange, shouldDelayFocus}) {
const {translate} = useLocalize();

/**
Expand Down Expand Up @@ -41,8 +41,8 @@ function RoomNameInput({autoFocus, disabled, errorText, forwardedRef, value, onB
errorText={errorText}
maxLength={CONST.REPORT.MAX_ROOM_NAME_LENGTH}
keyboardType={keyboardType} // this is a bit hacky solution to a RN issue https://github.com/facebook/react-native/issues/27449
onBlur={onBlur}
autoFocus={autoFocus}
onBlur={() => isFocused && onBlur()}
autoFocus={isFocused && autoFocus}
autoCapitalize="none"
shouldDelayFocus={shouldDelayFocus}
/>
Expand Down
3 changes: 3 additions & 0 deletions src/components/RoomNameInput/roomNameInputPropTypes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
import {withNavigationFocusPropTypes} from '../withNavigationFocus';

const propTypes = {
/** Callback to execute when the text input is modified correctly */
Expand Down Expand Up @@ -27,6 +28,8 @@ const propTypes = {

/** Whether we should wait before focusing the TextInput, useful when using transitions on Android */
shouldDelayFocus: PropTypes.bool,

...withNavigationFocusPropTypes,
};

const defaultProps = {
Expand Down
31 changes: 13 additions & 18 deletions src/components/TabSelector/TabSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,20 @@ const defaultProps = {
},
};

const getIcon = (route) => {
const getIconAndTitle = (route, translate) => {
switch (route) {
case CONST.TAB.MANUAL:
return {icon: Expensicons.Pencil, title: translate('tabSelector.manual')};
case CONST.TAB.SCAN:
return Expensicons.Receipt;
return {icon: Expensicons.Receipt, title: translate('tabSelector.scan')};
case CONST.TAB.NEW_CHAT:
return {icon: Expensicons.User, title: translate('tabSelector.chat')};
case CONST.TAB.NEW_ROOM:
return {icon: Expensicons.Hashtag, title: translate('tabSelector.room')};
case CONST.TAB.DISTANCE:
return Expensicons.Car;
return {icon: Expensicons.Car, title: translate('common.distance')};
default:
return Expensicons.Pencil;
}
};

const getTitle = (route, translate) => {
switch (route) {
case CONST.TAB.SCAN:
return translate('tabSelector.scan');
case CONST.TAB.DISTANCE:
return translate('common.distance');
default:
return translate('tabSelector.manual');
throw new Error(`Route ${route} has no icon nor title set.`);
}
};

Expand Down Expand Up @@ -94,8 +89,8 @@ function TabSelector({state, navigation, onTabPress, position}) {
const activeOpacity = getOpacity(position, state.routes.length, index, true);
const inactiveOpacity = getOpacity(position, state.routes.length, index, false);
const backgroundColor = getBackgroundColor(position, state.routes.length, index);

const isFocused = index === state.index;
const {icon, title} = getIconAndTitle(route.name, translate);

const onPress = () => {
if (isFocused) {
Expand All @@ -119,8 +114,8 @@ function TabSelector({state, navigation, onTabPress, position}) {
return (
<TabSelectorItem
key={route.name}
title={getTitle(route.name, translate)}
icon={getIcon(route.name)}
icon={icon}
title={title}
onPress={onPress}
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
Expand Down
21 changes: 15 additions & 6 deletions src/components/TextInput/BaseTextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ function BaseTextInput(props) {
const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry);
const [textInputWidth, setTextInputWidth] = useState(0);
const [textInputHeight, setTextInputHeight] = useState(0);
const [prefixWidth, setPrefixWidth] = useState(0);
const [height, setHeight] = useState(variables.componentSizeLarge);
const [width, setWidth] = useState();
const labelScale = useRef(new Animated.Value(initialActiveLabel ? styleConst.ACTIVE_LABEL_SCALE : styleConst.INACTIVE_LABEL_SCALE)).current;
Expand Down Expand Up @@ -221,9 +220,20 @@ function BaseTextInput(props) {
setPasswordHidden((prevPasswordHidden) => !prevPasswordHidden);
}, []);

const storePrefixLayoutDimensions = useCallback((event) => {
setPrefixWidth(Math.abs(event.nativeEvent.layout.width));
}, []);
// When adding a new prefix character, adjust this method to add expected character width.
// This is because character width isn't known before it's rendered to the screen, and once it's rendered,
// it's too late to calculate it's width because the change in padding would cause a visible jump.
// Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size
// also have an impact on the width of the character, but as long as there's only one font-family and one font-size,
// this method will produce reliable results.
const getCharacterPadding = (prefix) => {
switch (prefix) {
case CONST.POLICY.ROOM_PREFIX:
return 10;
default:
throw new Error(`Prefix ${prefix} has no padding assigned.`);
}
};

// eslint-disable-next-line react/forbid-foreign-prop-types
const inputProps = _.omit(props, _.keys(baseTextInputPropTypes.propTypes));
Expand Down Expand Up @@ -295,7 +305,6 @@ function BaseTextInput(props) {
pointerEvents="none"
selectable={false}
style={[styles.textInputPrefix, !hasLabel && styles.pv0]}
onLayout={storePrefixLayoutDimensions}
>
{props.prefixCharacter}
</Text>
Expand All @@ -322,7 +331,7 @@ function BaseTextInput(props) {
styles.w100,
props.inputStyle,
(!hasLabel || isMultiline) && styles.pv0,
props.prefixCharacter && StyleUtils.getPaddingLeft(prefixWidth + styles.pl1.paddingLeft),
props.prefixCharacter && StyleUtils.getPaddingLeft(getCharacterPadding(props.prefixCharacter) + styles.pl1.paddingLeft),
props.secureTextEntry && styles.secureInput,

// Explicitly remove `lineHeight` from single line inputs so that long text doesn't disappear
Expand Down
Loading

0 comments on commit 446f29b

Please sign in to comment.