From 915c5a5c1221a4c2cfb9f103a99db7ea71a30392 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 14 Jul 2022 15:16:28 -0600 Subject: [PATCH 01/14] Add OfflineWithFeedback component --- src/components/OfflineWithFeedback.js | 117 ++++++++++++++++++++ src/pages/workspace/WorkspaceMembersPage.js | 63 ++++++----- src/styles/styles.js | 41 +++++++ 3 files changed, 191 insertions(+), 30 deletions(-) create mode 100644 src/components/OfflineWithFeedback.js diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js new file mode 100644 index 000000000000..ff97a72baa32 --- /dev/null +++ b/src/components/OfflineWithFeedback.js @@ -0,0 +1,117 @@ +import React from 'react'; +import {Pressable, View} from 'react-native'; +import PropTypes from 'prop-types'; +import compose from '../libs/compose'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import {withNetwork} from './OnyxProvider'; +import networkPropTypes from './networkPropTypes'; +import Text from './Text'; +import styles from '../styles/styles'; +import Tooltip from './Tooltip'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; + +const propTypes = { + /** The type of action that's pending */ + pendingAction: PropTypes.oneOf(['add', 'update', 'delete']), + + /** The error to display */ + error: PropTypes.string, + + /** A function to run when the X button next to the error is clicked */ + onClose: PropTypes.func.isRequired, + + /** The content that needs offline feedback */ + children: PropTypes.node.isRequired, + + /** Information about the network */ + network: networkPropTypes.isRequired, + + /** Additional styles to add after local styles. Applied to Pressable portion of button */ + style: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.object), + PropTypes.object, + ]), + ...withLocalizePropTypes, +}; + +const defaultProps = { + pendingAction: null, + error: null, + style: [], +}; + +class OfflineWithFeedback extends React.Component { + constructor(props) { + super(props); + + this.state = { + }; + } + + applyStrikeThrough(children) { + return React.Children.map(children, (child) => { + if (!React.isValidElement(child)) { + return child; + } + + if (child.props.children) { + return React.cloneElement(child, { + textDecorationLine: 'line-through', + textDecorationStyle: 'solid', + children: this.applyStrikeThrough(child.props.children), + }); + } + + return React.cloneElement(child, styles.offlineFeedback.deleted); + }); + } + + render() { + const isOfflinePendingAction = this.props.network.isOffline && this.props.pendingAction; + const isUpdateOrDeleteError = this.props.error && (this.props.pendingAction === 'delete' || this.props.pendingAction === 'update'); + const isAddError = this.props.error && this.props.pendingAction === 'add'; + const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; + const needsStrikeThrough = this.props.network.isOffline && this.props.pendingAction === 'delete'; + const hideChildren = !this.props.network.isOffline && this.props.pendingAction === 'delete'; + let children = this.props.children; + + // Apply strikethrough to children if needed, but skip it if we are not going to render them + if (needsStrikeThrough && !hideChildren) { + children = this.applyStrikeThrough(children); + } + return ( + + {!hideChildren && ( + + {children} + + )} + {this.props.error && ( + + + {this.props.error} + + + + + + + )} + + ); + } +} + +OfflineWithFeedback.propTypes = propTypes; +OfflineWithFeedback.defaultProps = defaultProps; + +export default compose( + withLocalize, + withNetwork(), +)(OfflineWithFeedback); diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 764da2999e21..249744673dc9 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -27,6 +27,7 @@ import CheckboxWithTooltip from '../../components/CheckboxWithTooltip'; import Hoverable from '../../components/Hoverable'; import withFullPolicy, {fullPolicyPropTypes, fullPolicyDefaultProps} from './withFullPolicy'; import CONST from '../../CONST'; +import OfflineWithFeedback from '../../components/OfflineWithFeedback'; const propTypes = { /** The personal details of the person who is logged in */ @@ -203,35 +204,36 @@ class WorkspaceMembersPage extends React.Component { }) { const canBeRemoved = this.props.policy.owner !== item.login && this.props.session.email !== item.login; return ( - this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}> - this.toggleUser(item.login)} - activeOpacity={0.7} - > - + this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}> + this.toggleUser(item.login)} - toggleTooltip={this.state.showTooltipForLogin === item.login} - text={this.props.translate('workspace.people.error.cannotRemove')} - /> - - this.toggleUser(item.login)} - forceTextUnreadStyle - isDisabled={!canBeRemoved} - option={{ - text: Str.removeSMSDomain(item.displayName), - alternateText: Str.removeSMSDomain(item.login), - participantsList: [item], - icons: [item.avatar], - keyForList: item.login, - }} + activeOpacity={0.7} + > + this.toggleUser(item.login)} + toggleTooltip={this.state.showTooltipForLogin === item.login} + text={this.props.translate('workspace.people.error.cannotRemove')} /> - - {this.props.session.email === item.login && ( + + this.toggleUser(item.login)} + forceTextUnreadStyle + isDisabled={!canBeRemoved} + option={{ + text: Str.removeSMSDomain(item.displayName), + alternateText: Str.removeSMSDomain(item.login), + participantsList: [item], + icons: [item.avatar], + keyForList: item.login, + }} + /> + + {this.props.session.email === item.login && ( @@ -239,9 +241,10 @@ class WorkspaceMembersPage extends React.Component { - )} - - + )} + + + ); } diff --git a/src/styles/styles.js b/src/styles/styles.js index 482a70f56ca3..0a8d7217d821 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2335,6 +2335,9 @@ const styles = { width: '100%', flexDirection: 'row', justifyContent: 'space-between', + }, + + peopleRowOfflineFeedback: { borderBottomWidth: 1, borderColor: themeColors.border, ...spacing.pv2, @@ -2356,6 +2359,44 @@ const styles = { ...whiteSpace.noWrap, }, + offlineFeedback: { + deleted: { + textDecorationLine: 'line-through', + textDecorationStyle: 'solid', + }, + pending: { + opacity: 0.5, + }, + error: { + flexDirection: 'row', + justifyContent: 'center', + }, + container: { + ...spacing.pv2, + }, + text: { + color: themeColors.textSupporting, + flex: 1, + textAlign: 'center', + textAlignVertical: 'center', + }, + close: { + marginTop: 'auto', + marginBottom: 'auto', + }, + errorDot: { + borderColor: themeColors.textError, + borderRadius: 6, + borderWidth: 2, + height: 12, + width: 12, + marginTop: 'auto', + marginBottom: 'auto', + marginRight: 8, + backgroundColor: themeColors.textError, + }, + }, + sidebarPopover: { width: variables.sideBarWidth - 68, }, From bd5500d2825c80338d70ee9ad35c52c82a6c81b0 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 14 Jul 2022 16:49:16 -0600 Subject: [PATCH 02/14] Fix strikethrough and some styling issues --- src/components/OfflineWithFeedback.js | 8 +++++--- src/pages/workspace/WorkspaceMembersPage.js | 2 +- src/styles/StyleUtils.js | 14 ++++++++++++++ src/styles/styles.js | 4 +--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index ff97a72baa32..73b45697b840 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -10,6 +10,7 @@ import styles from '../styles/styles'; import Tooltip from './Tooltip'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import {combineStyles} from '../styles/StyleUtils'; const propTypes = { /** The type of action that's pending */ @@ -57,13 +58,14 @@ class OfflineWithFeedback extends React.Component { if (child.props.children) { return React.cloneElement(child, { - textDecorationLine: 'line-through', - textDecorationStyle: 'solid', + style: combineStyles(child.props.style, styles.offlineFeedback.deleted), children: this.applyStrikeThrough(child.props.children), }); } - return React.cloneElement(child, styles.offlineFeedback.deleted); + return React.cloneElement(child, { + style: combineStyles(child.props.style, styles.offlineFeedback.deleted), + }); }); } diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 249744673dc9..0c9149a0c730 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -204,7 +204,7 @@ class WorkspaceMembersPage extends React.Component { }) { const canBeRemoved = this.props.policy.owner !== item.login && this.props.session.email !== item.login; return ( - + this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}> { + finalStyles = finalStyles.concat(parseStyleAsArray(style)); + }); + return finalStyles; +} + /** * Get variable padding-left as style * @param {Number} paddingLeft @@ -474,5 +487,6 @@ export { getMiniReportActionContextMenuWrapperStyle, getPaymentMethodMenuWidth, parseStyleAsArray, + combineStyles, getPaddingLeft, }; diff --git a/src/styles/styles.js b/src/styles/styles.js index 0a8d7217d821..ea8ac51f5480 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2370,6 +2370,7 @@ const styles = { error: { flexDirection: 'row', justifyContent: 'center', + alignItems: 'center', }, container: { ...spacing.pv2, @@ -2377,7 +2378,6 @@ const styles = { text: { color: themeColors.textSupporting, flex: 1, - textAlign: 'center', textAlignVertical: 'center', }, close: { @@ -2390,8 +2390,6 @@ const styles = { borderWidth: 2, height: 12, width: 12, - marginTop: 'auto', - marginBottom: 'auto', marginRight: 8, backgroundColor: themeColors.textError, }, From 706bd197f21a8794d86bc55445bc7f3f966cd454 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 14 Jul 2022 17:38:37 -0600 Subject: [PATCH 03/14] Fix style error, simplify code --- src/components/OfflineWithFeedback.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 73b45697b840..bf32eca4ab54 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -10,7 +10,7 @@ import styles from '../styles/styles'; import Tooltip from './Tooltip'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import {combineStyles} from '../styles/StyleUtils'; +import * as StyleUtils from '../styles/StyleUtils'; const propTypes = { /** The type of action that's pending */ @@ -56,16 +56,12 @@ class OfflineWithFeedback extends React.Component { return child; } + const props = {style: StyleUtils.combineStyles(child.props.style, styles.offlineFeedback.deleted)}; if (child.props.children) { - return React.cloneElement(child, { - style: combineStyles(child.props.style, styles.offlineFeedback.deleted), - children: this.applyStrikeThrough(child.props.children), - }); + props.children = this.applyStrikeThrough(child.props.children); } - return React.cloneElement(child, { - style: combineStyles(child.props.style, styles.offlineFeedback.deleted), - }); + return React.cloneElement(child, props); }); } From 1374146adf45a4ef4f1a280a2fde527670ed5f17 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 14 Jul 2022 17:47:11 -0600 Subject: [PATCH 04/14] Remove unnecesary styles, fix delete casse with error --- src/components/OfflineWithFeedback.js | 4 ++-- src/styles/styles.js | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index bf32eca4ab54..a488c202a990 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -71,7 +71,7 @@ class OfflineWithFeedback extends React.Component { const isAddError = this.props.error && this.props.pendingAction === 'add'; const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; const needsStrikeThrough = this.props.network.isOffline && this.props.pendingAction === 'delete'; - const hideChildren = !this.props.network.isOffline && this.props.pendingAction === 'delete'; + const hideChildren = !this.props.network.isOffline && this.props.pendingAction === 'delete' && !this.props.error; let children = this.props.children; // Apply strikethrough to children if needed, but skip it if we are not going to render them @@ -89,7 +89,7 @@ class OfflineWithFeedback extends React.Component { {this.props.error} - + Date: Fri, 15 Jul 2022 14:36:20 -0600 Subject: [PATCH 05/14] Make component stateless, add link to readme --- README.md | 2 + src/components/OfflineWithFeedback.js | 111 ++++++++++++-------------- 2 files changed, 52 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 5b747b9b69ea..2fa0828e8c14 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ You can use any IDE or code editing tool for developing on any platform. Use you For an M1 Mac, read this [SO](https://stackoverflow.com/c/expensify/questions/11580) for installing cocoapods. * To install the iOS dependencies, run: `npm install && cd ios/ && pod install && cd ..` +* If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699) * To run a on a **Development Simulator**: `npm run ios` * Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile @@ -52,6 +53,7 @@ For an M1 Mac, read this [SO](https://stackoverflow.com/c/expensify/questions/11 ## Running the Android app 🤖 * To install the Android dependencies, run: `npm install` * Go through the instructions on [this page](https://reactnative.dev/docs/environment-setup#development-os) for "React Native CLI Quickstart" > Mac OS > Android +* If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699) * To run a on a **Development Emulator**: `npm run android` * Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index a488c202a990..11c7532a7048 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -42,70 +42,59 @@ const defaultProps = { style: [], }; -class OfflineWithFeedback extends React.Component { - constructor(props) { - super(props); - - this.state = { - }; - } - - applyStrikeThrough(children) { - return React.Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return child; - } - - const props = {style: StyleUtils.combineStyles(child.props.style, styles.offlineFeedback.deleted)}; - if (child.props.children) { - props.children = this.applyStrikeThrough(child.props.children); - } - - return React.cloneElement(child, props); - }); - } - - render() { - const isOfflinePendingAction = this.props.network.isOffline && this.props.pendingAction; - const isUpdateOrDeleteError = this.props.error && (this.props.pendingAction === 'delete' || this.props.pendingAction === 'update'); - const isAddError = this.props.error && this.props.pendingAction === 'add'; - const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; - const needsStrikeThrough = this.props.network.isOffline && this.props.pendingAction === 'delete'; - const hideChildren = !this.props.network.isOffline && this.props.pendingAction === 'delete' && !this.props.error; - let children = this.props.children; - - // Apply strikethrough to children if needed, but skip it if we are not going to render them - if (needsStrikeThrough && !hideChildren) { - children = this.applyStrikeThrough(children); +function applyStrikeThrough(children) { + return React.Children.map(children, (child) => { + if (!React.isValidElement(child)) { + return child; } - return ( - - {!hideChildren && ( - - {children} - - )} - {this.props.error && ( - - - {this.props.error} - - - - - - - )} - - ); - } + const props = {style: StyleUtils.combineStyles(child.props.style, styles.offlineFeedback.deleted)}; + if (child.props.children) { + props.children = applyStrikeThrough(child.props.children); + } + return React.cloneElement(child, props); + }); } +const OfflineWithFeedback = (props) => { + const isOfflinePendingAction = props.network.isOffline && props.pendingAction; + const isUpdateOrDeleteError = props.error && (props.pendingAction === 'delete' || props.pendingAction === 'update'); + const isAddError = props.error && props.pendingAction === 'add'; + const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; + const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete'; + const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !props.error; + let children = props.children; + + // Apply strikethrough to children if needed, but skip it if we are not going to render them + if (needsStrikeThrough && !hideChildren) { + children = applyStrikeThrough(children); + } + return ( + + {!hideChildren && ( + + {children} + + )} + {props.error && ( + + + {props.error} + + + + + + + )} + + ); +}; + OfflineWithFeedback.propTypes = propTypes; OfflineWithFeedback.defaultProps = defaultProps; From edcdb2067c21fdc1b91ed7dd17d091f23ebe3ce9 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Mon, 18 Jul 2022 17:31:33 -0600 Subject: [PATCH 06/14] Use svg and adjust styling a bit --- src/components/Icon/Expensicons.js | 2 ++ src/components/OfflineWithFeedback.js | 5 ++++- src/styles/styles.js | 9 ++------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js index 0b11a5602b13..15f0f3282d43 100644 --- a/src/components/Icon/Expensicons.js +++ b/src/components/Icon/Expensicons.js @@ -19,6 +19,7 @@ import ClosedSign from '../../../assets/images/closed-sign.svg'; import Collapse from '../../../assets/images/collapse.svg'; import Concierge from '../../../assets/images/concierge.svg'; import CreditCard from '../../../assets/images/creditcard.svg'; +import DotIndicator from '../../../assets/images/dot-indicator.svg'; import DownArrow from '../../../assets/images/down.svg'; import Download from '../../../assets/images/download.svg'; import Emoji from '../../../assets/images/emoji.svg'; @@ -110,6 +111,7 @@ export { CreditCard, DeletedRoomAvatar, DomainRoomAvatar, + DotIndicator, DownArrow, Download, Emoji, diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 11c7532a7048..8db6b0c219db 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -11,6 +11,7 @@ import Tooltip from './Tooltip'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import * as StyleUtils from '../styles/StyleUtils'; +import colors from '../styles/colors'; const propTypes = { /** The type of action that's pending */ @@ -77,7 +78,9 @@ const OfflineWithFeedback = (props) => { )} {props.error && ( - + + + {props.error} Date: Tue, 19 Jul 2022 11:28:33 -0600 Subject: [PATCH 07/14] Add missing asset --- assets/images/dot-indicator.svg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 assets/images/dot-indicator.svg diff --git a/assets/images/dot-indicator.svg b/assets/images/dot-indicator.svg new file mode 100644 index 000000000000..49be497b35c1 --- /dev/null +++ b/assets/images/dot-indicator.svg @@ -0,0 +1,6 @@ + + + + + From 9276eb8e4da8b51e1e621c8c96b4fd985c46b7fe Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 19 Jul 2022 15:29:39 -0600 Subject: [PATCH 08/14] Make OfflineWithFeedback work on the OptionRow and remove test code --- src/components/OfflineWithFeedback.js | 11 +++ src/components/OptionRow.js | 11 +-- src/pages/workspace/WorkspaceMembersPage.js | 74 ++++++++++----------- 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 8db6b0c219db..2f581552f3cb 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -13,6 +13,12 @@ import * as Expensicons from './Icon/Expensicons'; import * as StyleUtils from '../styles/StyleUtils'; import colors from '../styles/colors'; +/** + * This component should be used when we are using the offline pattern B (offline with feedback). + * You should enclose any element that should have feedback that the action was taken offline and it will take + * care of adding the appropriate styles for pending actions and displaying the dismissible error. + */ + const propTypes = { /** The type of action that's pending */ pendingAction: PropTypes.oneOf(['add', 'update', 'delete']), @@ -43,6 +49,11 @@ const defaultProps = { style: [], }; +/** + * This method applies the strikethrough to all the children passed recursively + * @param {Array} children + * @return {Array} + */ function applyStrikeThrough(children) { return React.Children.map(children, (child) => { if (!React.isValidElement(child)) { diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index 61901bec3050..a6a75bceb48a 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -62,6 +62,8 @@ const propTypes = { /** Whether this option should be disabled */ isDisabled: PropTypes.bool, + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + ...withLocalizePropTypes, }; @@ -77,6 +79,7 @@ const defaultProps = { onSelectRow: () => {}, isDisabled: false, optionIsFocused: false, + style: null, }; const OptionRow = (props) => { @@ -86,12 +89,12 @@ const OptionRow = (props) => { : styles.sidebarLinkText; const textUnreadStyle = (props.option.isUnread || props.forceTextUnreadStyle) ? [textStyle, styles.sidebarLinkTextUnread] : [textStyle]; - const displayNameStyle = props.mode === CONST.OPTION_MODE.COMPACT + const displayNameStyle = StyleUtils.combineStyles(props.mode === CONST.OPTION_MODE.COMPACT ? [styles.optionDisplayName, ...textUnreadStyle, styles.optionDisplayNameCompact, styles.mr2] - : [styles.optionDisplayName, ...textUnreadStyle]; - const alternateTextStyle = props.mode === CONST.OPTION_MODE.COMPACT + : [styles.optionDisplayName, ...textUnreadStyle], props.style); + const alternateTextStyle = StyleUtils.combineStyles(props.mode === CONST.OPTION_MODE.COMPACT ? [textStyle, styles.optionAlternateText, styles.textLabelSupporting, styles.optionAlternateTextCompact] - : [textStyle, styles.optionAlternateText, styles.textLabelSupporting]; + : [textStyle, styles.optionAlternateText, styles.textLabelSupporting], props.style); const contentContainerStyles = props.mode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, styles.alignItemsCenter] : [styles.flex1]; diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 0c9149a0c730..8775b4fd2060 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -204,47 +204,45 @@ class WorkspaceMembersPage extends React.Component { }) { const canBeRemoved = this.props.policy.owner !== item.login && this.props.session.email !== item.login; return ( - - this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}> - this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}> + this.toggleUser(item.login)} + activeOpacity={0.7} + > + this.toggleUser(item.login)} - activeOpacity={0.7} - > - this.toggleUser(item.login)} - toggleTooltip={this.state.showTooltipForLogin === item.login} - text={this.props.translate('workspace.people.error.cannotRemove')} + toggleTooltip={this.state.showTooltipForLogin === item.login} + text={this.props.translate('workspace.people.error.cannotRemove')} + /> + + this.toggleUser(item.login)} + forceTextUnreadStyle + isDisabled={!canBeRemoved} + option={{ + text: Str.removeSMSDomain(item.displayName), + alternateText: Str.removeSMSDomain(item.login), + participantsList: [item], + icons: [item.avatar], + keyForList: item.login, + }} /> - - this.toggleUser(item.login)} - forceTextUnreadStyle - isDisabled={!canBeRemoved} - option={{ - text: Str.removeSMSDomain(item.displayName), - alternateText: Str.removeSMSDomain(item.login), - participantsList: [item], - icons: [item.avatar], - keyForList: item.login, - }} - /> - - {this.props.session.email === item.login && ( - - - - {this.props.translate('common.admin')} - - + + {this.props.session.email === item.login && ( + + + + {this.props.translate('common.admin')} + - )} - - - + + )} + + ); } From 6b382bec5df5b2dce2a58bac1f52d0b8bcc6b2c7 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Tue, 19 Jul 2022 15:36:34 -0600 Subject: [PATCH 09/14] Remove unused import --- src/pages/workspace/WorkspaceMembersPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 8775b4fd2060..d46dff0872b5 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -27,7 +27,6 @@ import CheckboxWithTooltip from '../../components/CheckboxWithTooltip'; import Hoverable from '../../components/Hoverable'; import withFullPolicy, {fullPolicyPropTypes, fullPolicyDefaultProps} from './withFullPolicy'; import CONST from '../../CONST'; -import OfflineWithFeedback from '../../components/OfflineWithFeedback'; const propTypes = { /** The personal details of the person who is logged in */ From e7df3c6d90839020cd287dc8ce5d0379a0bfc047 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 21 Jul 2022 17:31:16 -0600 Subject: [PATCH 10/14] Support multiple errors and fix indentation --- src/components/OfflineWithFeedback.js | 22 +++++++++++++-------- src/pages/workspace/WorkspaceMembersPage.js | 13 ++++++------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 2f581552f3cb..54cde4a5d6e9 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import React from 'react'; import {Pressable, View} from 'react-native'; import PropTypes from 'prop-types'; @@ -23,8 +24,9 @@ const propTypes = { /** The type of action that's pending */ pendingAction: PropTypes.oneOf(['add', 'update', 'delete']), - /** The error to display */ - error: PropTypes.string, + /** The errors to display */ + // eslint-disable-next-line react/forbid-prop-types + errors: PropTypes.object, /** A function to run when the X button next to the error is clicked */ onClose: PropTypes.func.isRequired, @@ -45,7 +47,7 @@ const propTypes = { const defaultProps = { pendingAction: null, - error: null, + errors: null, style: [], }; @@ -69,11 +71,11 @@ function applyStrikeThrough(children) { const OfflineWithFeedback = (props) => { const isOfflinePendingAction = props.network.isOffline && props.pendingAction; - const isUpdateOrDeleteError = props.error && (props.pendingAction === 'delete' || props.pendingAction === 'update'); - const isAddError = props.error && props.pendingAction === 'add'; + const isUpdateOrDeleteError = props.errors && (props.pendingAction === 'delete' || props.pendingAction === 'update'); + const isAddError = props.errors && props.pendingAction === 'add'; const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete'; - const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !props.error; + const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !props.errors; let children = props.children; // Apply strikethrough to children if needed, but skip it if we are not going to render them @@ -87,12 +89,16 @@ const OfflineWithFeedback = (props) => { {children} )} - {props.error && ( + {props.errors && ( - {props.error} + + {_.map(props.errors, error => ( + {error} + ))} + {this.props.session.email === item.login && ( - - - - {this.props.translate('common.admin')} - + + + + {this.props.translate('common.admin')} + + - )} From 740fd19d9677f24670e5a48c5ca576bc80ede8bb Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 21 Jul 2022 18:06:46 -0600 Subject: [PATCH 11/14] Sort errors --- src/components/OfflineWithFeedback.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 54cde4a5d6e9..951d614b95b6 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -77,6 +77,7 @@ const OfflineWithFeedback = (props) => { const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete'; const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !props.errors; let children = props.children; + const sortedErrors = _.map(_.sortBy(_.keys(props.errors)), key => props.errors[key]); // Apply strikethrough to children if needed, but skip it if we are not going to render them if (needsStrikeThrough && !hideChildren) { @@ -95,7 +96,7 @@ const OfflineWithFeedback = (props) => { - {_.map(props.errors, error => ( + {_.map(sortedErrors, error => ( {error} ))} From 7daf9cb89777144a85e4680f48e27be3ed07cb05 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 21 Jul 2022 18:37:32 -0600 Subject: [PATCH 12/14] Lint --- src/pages/workspace/WorkspaceMembersPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 33990878cffb..764da2999e21 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -27,7 +27,6 @@ import CheckboxWithTooltip from '../../components/CheckboxWithTooltip'; import Hoverable from '../../components/Hoverable'; import withFullPolicy, {fullPolicyPropTypes, fullPolicyDefaultProps} from './withFullPolicy'; import CONST from '../../CONST'; -import OfflineWithFeedback from '../../components/OfflineWithFeedback'; const propTypes = { /** The personal details of the person who is logged in */ From 045a6da2e21605a63d28929fd23a5fa510de5023 Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 22 Jul 2022 13:26:19 -0600 Subject: [PATCH 13/14] Align errors to the left --- src/components/OfflineWithFeedback.js | 12 ++++++++---- src/styles/styles.js | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 951d614b95b6..09ce46678219 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -77,7 +77,11 @@ const OfflineWithFeedback = (props) => { const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete'; const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !props.errors; let children = props.children; - const sortedErrors = _.map(_.sortBy(_.keys(props.errors)), key => props.errors[key]); + const sortedErrors = _.chain(props.errors) + .keys() + .sortBy() + .map(key => props.errors[key]) + .value(); // Apply strikethrough to children if needed, but skip it if we are not going to render them if (needsStrikeThrough && !hideChildren) { @@ -95,9 +99,9 @@ const OfflineWithFeedback = (props) => { - - {_.map(sortedErrors, error => ( - {error} + + {_.map(sortedErrors, (error, i) => ( + {error} ))} diff --git a/src/styles/styles.js b/src/styles/styles.js index b3b80c4bb01c..3a222ead81f8 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2377,6 +2377,10 @@ const styles = { container: { ...spacing.pv2, }, + textContainer: { + flexDirection: 'column', + flex: 1, + }, text: { color: themeColors.textSupporting, flex: 1, From 1bb5c7ba9f5f98f7cd0a78718d2b3c766db6902e Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Fri, 22 Jul 2022 14:48:56 -0600 Subject: [PATCH 14/14] Check for errors in a better way --- src/components/OfflineWithFeedback.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 09ce46678219..196a4da3d403 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -13,6 +13,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import * as StyleUtils from '../styles/StyleUtils'; import colors from '../styles/colors'; +import variables from '../styles/variables'; /** * This component should be used when we are using the offline pattern B (offline with feedback). @@ -70,12 +71,13 @@ function applyStrikeThrough(children) { } const OfflineWithFeedback = (props) => { + const hasErrors = !_.isEmpty(props.errors); const isOfflinePendingAction = props.network.isOffline && props.pendingAction; - const isUpdateOrDeleteError = props.errors && (props.pendingAction === 'delete' || props.pendingAction === 'update'); - const isAddError = props.errors && props.pendingAction === 'add'; + const isUpdateOrDeleteError = hasErrors && (props.pendingAction === 'delete' || props.pendingAction === 'update'); + const isAddError = hasErrors && props.pendingAction === 'add'; const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete'; - const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !props.errors; + const hideChildren = !props.network.isOffline && props.pendingAction === 'delete' && !hasErrors; let children = props.children; const sortedErrors = _.chain(props.errors) .keys() @@ -94,10 +96,10 @@ const OfflineWithFeedback = (props) => { {children} )} - {props.errors && ( + {hasErrors && ( - + {_.map(sortedErrors, (error, i) => (