Skip to content
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

Categories UI/UX #26501

Merged
merged 57 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9de59bd
connect story to components
rezkiy37 Aug 24, 2023
0de8fc9
Merge branch 'feature/24463-category-helpers' of https://github.com/r…
rezkiy37 Aug 24, 2023
a4e8623
connect a helper
rezkiy37 Aug 24, 2023
ce8519c
fix lint warnings
rezkiy37 Aug 25, 2023
4271f61
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Aug 25, 2023
df09636
fix text colors
rezkiy37 Aug 25, 2023
3136cba
fix helpers
rezkiy37 Aug 25, 2023
8ab4fa4
fix error
rezkiy37 Aug 25, 2023
16584e9
add selected categories
rezkiy37 Aug 25, 2023
ed22582
implement selected ui
rezkiy37 Aug 25, 2023
9ec3adf
integrate search
rezkiy37 Aug 25, 2023
e84005d
unselect category
rezkiy37 Aug 25, 2023
b7a28e1
Merge branch 'feature/24463-category-helpers' of https://github.com/r…
rezkiy37 Aug 28, 2023
c1bedca
implement recently used categories
rezkiy37 Aug 28, 2023
24b3f85
fix selected state
rezkiy37 Aug 28, 2023
b8acd96
add translations
rezkiy37 Aug 28, 2023
678b4c6
add category to a request
rezkiy37 Aug 28, 2023
2b481b2
fix prop types
rezkiy37 Aug 28, 2023
9064496
fix option row height
rezkiy37 Aug 28, 2023
8ef7ad3
add types for onyx categories
rezkiy37 Aug 29, 2023
b2bd62e
improve category picker
rezkiy37 Aug 29, 2023
30210f7
improve option row styles
rezkiy37 Aug 29, 2023
5212989
enable all tests
rezkiy37 Aug 29, 2023
c6eaa66
add comment
rezkiy37 Aug 29, 2023
46ef605
simplify setMoneyRequestCategory
rezkiy37 Aug 29, 2023
136db9d
Merge branch 'feature/24463-category-helpers' of https://github.com/r…
rezkiy37 Aug 29, 2023
323184b
remove unused var
rezkiy37 Aug 29, 2023
e96b876
Revert "simplify setMoneyRequestCategory"
rezkiy37 Aug 29, 2023
a83a539
fix recently categories work
rezkiy37 Aug 29, 2023
b1c72c4
integrate index offset
rezkiy37 Aug 29, 2023
a688c13
Merge branch 'feature/24463-category-helpers' of https://github.com/r…
rezkiy37 Aug 29, 2023
d2cf77a
improve multiline enabling
rezkiy37 Aug 29, 2023
f89e2f3
search only for large list
rezkiy37 Aug 29, 2023
1d1ee6d
improve initial focus index
rezkiy37 Aug 29, 2023
75e808d
perform up of category picker
rezkiy37 Aug 29, 2023
099c1fc
Merge branch 'feature/24463-category-helpers' of https://github.com/r…
rezkiy37 Aug 31, 2023
c2ad63e
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 1, 2023
b55359e
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 4, 2023
0e0f317
remove redundant key
rezkiy37 Sep 4, 2023
e49f2dd
minor improvement
rezkiy37 Sep 4, 2023
d7e1560
enable category selection for distance requests
rezkiy37 Sep 4, 2023
c2980fe
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 5, 2023
278b103
rename var
rezkiy37 Sep 5, 2023
d59ddb1
fix onyx types
rezkiy37 Sep 5, 2023
4cdf856
rename var
rezkiy37 Sep 5, 2023
10a711a
Merge branch 'main' into feature/24463-categories-ui-ux
BeeMargarida Sep 7, 2023
c6f425f
Merge remote-tracking branch 'origin/yuwen-policyRecentlyUsedCategori…
BeeMargarida Sep 8, 2023
90dd075
fix: remove recent categories old key and rename based on the new key
BeeMargarida Sep 8, 2023
748dcfc
feat: add permission check to show money request categories
BeeMargarida Sep 8, 2023
5acfae0
refactor: move check to variable
BeeMargarida Sep 8, 2023
b5d1657
feat: move recently used categories save to IOU requestMoney
BeeMargarida Sep 8, 2023
c3a77d1
fix: jsdoc
BeeMargarida Sep 8, 2023
0fbc6d1
fix: simplify code and remove unnecessary undefined
BeeMargarida Sep 11, 2023
1a7cb65
refactor: simplify function
BeeMargarida Sep 11, 2023
3d47748
fix: correct jsdoc for categories
BeeMargarida Sep 11, 2023
305b6a2
docs: documentation for policy category
BeeMargarida Sep 12, 2023
b61d13a
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ const CONST = {
TASKS: 'tasks',
THREADS: 'threads',
CUSTOM_STATUS: 'customStatus',
NEW_DOT_CATEGORIES: 'newDotCategories',
},
BUTTON_STATES: {
DEFAULT: 'default',
Expand Down
2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ type OnyxValues = {
// Collections
[ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download;
[ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy;
[ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: unknown;
[ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory;
[ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories;
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
[ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember;
Expand Down
14 changes: 13 additions & 1 deletion src/components/CategoryPicker/categoryPickerPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,23 @@ const propTypes = {
/* Onyx Props */
/** Collection of categories attached to a policy */
policyCategories: PropTypes.objectOf(categoryPropTypes),

/* Onyx Props */
/** Collection of recently used categories attached to a policy */
policyRecentlyUsedCategories: PropTypes.arrayOf(PropTypes.string),

/* Onyx Props */
/** Holds data related to Money Request view state, rather than the underlying Money Request data. */
iou: PropTypes.shape({
category: PropTypes.string.isRequired,
}),
};

const defaultProps = {
policyID: '',
policyCategories: null,
policyCategories: {},
policyRecentlyUsedCategories: [],
iou: {},
};

export {propTypes, defaultProps};
99 changes: 73 additions & 26 deletions src/components/CategoryPicker/index.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,88 @@
import React, {useMemo} from 'react';
import _ from 'underscore';
import React, {useMemo, useState} from 'react';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import ONYXKEYS from '../../ONYXKEYS';
import {propTypes, defaultProps} from './categoryPickerPropTypes';
import OptionsList from '../OptionsList';
import styles from '../../styles/styles';
import ScreenWrapper from '../ScreenWrapper';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
import CONST from '../../CONST';
import * as IOU from '../../libs/actions/IOU';
import * as OptionsListUtils from '../../libs/OptionsListUtils';
import OptionsSelector from '../OptionsSelector';
import useLocalize from '../../hooks/useLocalize';

function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentlyUsedCategories}) {
const {translate} = useLocalize();
const [searchValue, setSearchValue] = useState('');

const policyCategoriesCount = _.size(policyCategories);
const isCategoriesCountBelowThreshold = policyCategoriesCount < CONST.CATEGORY_LIST_THRESHOLD;
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved

function CategoryPicker({policyCategories, reportID, iouType}) {
const sections = useMemo(() => {
const categoryList = _.chain(policyCategories)
.values()
.map((category) => ({
text: category.name,
keyForList: category.name,
tooltipText: category.name,
}))
.value();
const selectedOptions = useMemo(() => {
if (!iou.category) {
return [];
}

return [
{
data: categoryList,
name: iou.category,
enabled: true,
accountID: null,
},
];
}, [policyCategories]);
}, [iou.category]);

const initialFocusedIndex = useMemo(() => {
if (isCategoriesCountBelowThreshold && selectedOptions.length > 0) {
return _.chain(policyCategories)
.values()
.findIndex((category) => category.name === selectedOptions[0].name, true)
.value();
}

return 0;
}, [policyCategories, selectedOptions, isCategoriesCountBelowThreshold]);

const sections = useMemo(
() => OptionsListUtils.getNewChatOptions({}, {}, [], searchValue, selectedOptions, [], false, false, true, policyCategories, policyRecentlyUsedCategories, false).categoryOptions,
[policyCategories, policyRecentlyUsedCategories, searchValue, selectedOptions],
);

const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, searchValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, OptionsListUtils validates if the searchValue is an email or phone.
We should have changed this behavior to only check if it matches any category.

issue: #28078

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rushatgabhane, is it fixed already or I can work on it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's fixed in the linked issue

const shouldShowTextInput = !isCategoriesCountBelowThreshold;

const navigateBack = () => {
Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID));
};

const updateCategory = (category) => {
if (category.searchText === iou.category) {
IOU.resetMoneyRequestCategory();
} else {
IOU.setMoneyRequestCategory(category.searchText);
}

navigateBack();
};

return (
<ScreenWrapper includeSafeAreaPaddingBottom={false}>
{({safeAreaPaddingBottomStyle}) => (
<OptionsList
optionHoveredStyle={styles.hoveredComponentBG}
contentContainerStyles={[safeAreaPaddingBottomStyle]}
sections={sections}
onSelectRow={navigateBack}
/>
)}
</ScreenWrapper>
<OptionsSelector
optionHoveredStyle={styles.hoveredComponentBG}
sections={sections}
selectedOptions={selectedOptions}
value={searchValue}
initialFocusedIndex={initialFocusedIndex}
headerMessage={headerMessage}
shouldShowTextInput={shouldShowTextInput}
textInputLabel={translate('common.search')}
boldStyle
highlightSelectedOptions
isRowMultilineSupported
onChangeText={setSearchValue}
onSelectRow={updateCategory}
/>
);
}

Expand All @@ -53,4 +94,10 @@ export default withOnyx({
policyCategories: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
},
policyRecentlyUsedCategories: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`,
},
iou: {
key: ONYXKEYS.IOU,
},
})(CategoryPicker);
12 changes: 10 additions & 2 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import ConfirmedRoute from './ConfirmedRoute';
import transactionPropTypes from './transactionPropTypes';
import DistanceRequestUtils from '../libs/DistanceRequestUtils';
import * as IOU from '../libs/actions/IOU';
import Permissions from '../libs/Permissions';

const propTypes = {
/** Callback to inform parent modal of success */
Expand Down Expand Up @@ -90,6 +91,9 @@ const propTypes = {
email: PropTypes.string.isRequired,
}),

/** List of betas available to current user */
betas: PropTypes.arrayOf(PropTypes.string),

/** The policyID of the request */
policyID: PropTypes.string,

Expand Down Expand Up @@ -144,6 +148,7 @@ const defaultProps = {
session: {
email: null,
},
betas: [],
policyID: '',
reportID: '',
...withCurrentUserPersonalDetailsDefaultProps,
Expand Down Expand Up @@ -171,7 +176,7 @@ function MoneyRequestConfirmationList(props) {
const {unit, rate, currency} = props.mileageRate;
const distance = lodashGet(transaction, 'routes.route0.distance', 0);
const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0;
const shouldCategoryEditable = !_.isEmpty(props.policyCategories) && !props.isDistanceRequest;
const shouldCategoryBeEditable = !_.isEmpty(props.policyCategories) && Permissions.canUseCategories(props.betas);

const formattedAmount = CurrencyUtils.convertToDisplayString(
shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount,
Expand Down Expand Up @@ -484,7 +489,7 @@ function MoneyRequestConfirmationList(props) {
disabled={didConfirm || props.isReadOnly || !isTypeRequest}
/>
)}
{shouldCategoryEditable && (
{shouldCategoryBeEditable && (
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly}
title={props.iouCategory}
Expand All @@ -509,6 +514,9 @@ export default compose(
session: {
key: ONYXKEYS.SESSION,
},
betas: {
key: ONYXKEYS.BETAS,
},
policyCategories: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
},
Expand Down
23 changes: 21 additions & 2 deletions src/components/OptionRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const propTypes = {
/** Whether we should show the selected state */
showSelectedState: PropTypes.bool,

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

/** Whether this item is selected */
isSelected: PropTypes.bool,

Expand All @@ -57,6 +60,9 @@ const propTypes = {
/** Whether to remove the lateral padding and align the content with the margins */
shouldDisableRowInnerPadding: PropTypes.bool,

/** Whether to wrap large text up to 2 lines */
isMultilineSupported: PropTypes.bool,

style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),

...withLocalizePropTypes,
Expand All @@ -65,12 +71,14 @@ const propTypes = {
const defaultProps = {
hoverStyle: styles.sidebarLinkHover,
showSelectedState: false,
highlightSelected: false,
isSelected: false,
boldStyle: false,
showTitleTooltip: false,
onSelectRow: undefined,
isDisabled: false,
optionIsFocused: false,
isMultilineSupported: false,
style: null,
shouldHaveOptionSeparator: false,
shouldDisableRowInnerPadding: false,
Expand All @@ -89,9 +97,11 @@ class OptionRow extends Component {
return (
this.state.isDisabled !== nextState.isDisabled ||
this.props.isDisabled !== nextProps.isDisabled ||
this.props.isMultilineSupported !== nextProps.isMultilineSupported ||
this.props.isSelected !== nextProps.isSelected ||
this.props.shouldHaveOptionSeparator !== nextProps.shouldHaveOptionSeparator ||
this.props.showSelectedState !== nextProps.showSelectedState ||
this.props.highlightSelected !== nextProps.highlightSelected ||
this.props.showTitleTooltip !== nextProps.showTitleTooltip ||
!_.isEqual(this.props.option.icons, nextProps.option.icons) ||
this.props.optionIsFocused !== nextProps.optionIsFocused ||
Expand Down Expand Up @@ -119,7 +129,7 @@ class OptionRow extends Component {
let pressableRef = null;
const textStyle = this.props.optionIsFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText;
const textUnreadStyle = this.props.boldStyle || this.props.option.boldStyle ? [textStyle, styles.sidebarLinkTextBold] : [textStyle];
const displayNameStyle = StyleUtils.combineStyles(styles.optionDisplayName, textUnreadStyle, this.props.style, styles.pre);
const displayNameStyle = StyleUtils.combineStyles(styles.optionDisplayName, textUnreadStyle, this.props.style, styles.pre, this.state.isDisabled ? styles.optionRowDisabled : {});
const alternateTextStyle = StyleUtils.combineStyles(
textStyle,
styles.optionAlternateText,
Expand Down Expand Up @@ -182,6 +192,7 @@ class OptionRow extends Component {
this.props.optionIsFocused ? styles.sidebarLinkActive : null,
this.props.shouldHaveOptionSeparator && styles.borderTop,
!this.props.onSelectRow && !this.props.isDisabled ? styles.cursorDefault : null,
this.props.isSelected && this.props.highlightSelected && styles.optionRowSelected,
]}
accessibilityLabel={this.props.option.text}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
Expand Down Expand Up @@ -216,7 +227,7 @@ class OptionRow extends Component {
fullTitle={this.props.option.text}
displayNamesWithTooltips={displayNamesWithTooltips}
tooltipEnabled={this.props.showTitleTooltip}
numberOfLines={1}
numberOfLines={this.props.isMultilineSupported ? 2 : 1}
textStyles={displayNameStyle}
shouldUseFullTitle={
this.props.option.isChatRoom ||
Expand Down Expand Up @@ -249,6 +260,14 @@ class OptionRow extends Component {
</View>
)}
{this.props.showSelectedState && <SelectCircle isChecked={this.props.isSelected} />}
{this.props.isSelected && this.props.highlightSelected && (
<View style={styles.defaultCheckmarkWrapper}>
<Icon
src={Expensicons.Checkmark}
fill={themeColors.iconSuccessFill}
/>
</View>
)}
</View>
</View>
{Boolean(this.props.option.customIcon) && (
Expand Down
6 changes: 5 additions & 1 deletion src/components/OptionsList/BaseOptionsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ function BaseOptionsList({
shouldDisableRowInnerPadding,
disableFocusOptions,
canSelectMultipleOptions,
highlightSelectedOptions,
onSelectRow,
boldStyle,
isDisabled,
innerRef,
isRowMultilineSupported,
}) {
const flattenedData = useRef();
const previousSections = usePrevious(sections);
Expand Down Expand Up @@ -175,12 +177,14 @@ function BaseOptionsList({
hoverStyle={optionHoveredStyle}
optionIsFocused={!disableFocusOptions && !isItemDisabled && focusedIndex === index + section.indexOffset}
onSelectRow={onSelectRow}
isSelected={Boolean(_.find(selectedOptions, (option) => option.accountID === item.accountID))}
isSelected={Boolean(_.find(selectedOptions, (option) => option.accountID === item.accountID || option.name === item.searchText))}
showSelectedState={canSelectMultipleOptions}
highlightSelected={highlightSelectedOptions}
boldStyle={boldStyle}
isDisabled={isItemDisabled}
shouldHaveOptionSeparator={index > 0 && shouldHaveOptionSeparator}
shouldDisableRowInnerPadding={shouldDisableRowInnerPadding}
isMultilineSupported={isRowMultilineSupported}
/>
);
};
Expand Down
8 changes: 8 additions & 0 deletions src/components/OptionsList/optionsListPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const propTypes = {
/** Whether we can select multiple options or not */
canSelectMultipleOptions: PropTypes.bool,

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

/** Whether to show headers above each section or not */
hideSectionHeaders: PropTypes.bool,

Expand Down Expand Up @@ -78,6 +81,9 @@ const propTypes = {

/** Whether to show the scroll bar */
showScrollIndicator: PropTypes.bool,

/** Whether to wrap large text up to 2 lines */
isRowMultilineSupported: PropTypes.bool,
};

const defaultProps = {
Expand All @@ -88,6 +94,7 @@ const defaultProps = {
focusedIndex: 0,
selectedOptions: [],
canSelectMultipleOptions: false,
highlightSelectedOptions: false,
hideSectionHeaders: false,
disableFocusOptions: false,
boldStyle: false,
Expand All @@ -101,6 +108,7 @@ const defaultProps = {
shouldHaveOptionSeparator: false,
shouldDisableRowInnerPadding: false,
showScrollIndicator: false,
isRowMultilineSupported: false,
};

export {propTypes, defaultProps};
Loading
Loading