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

Show Product tooltip for Workspace participant on create expense #56041

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6617,6 +6617,7 @@ const CONST = {
LHN_WORKSPACE_CHAT_TOOLTIP: 'workspaceChatLHNTooltip',
GLOBAL_CREATE_TOOLTIP: 'globalCreateTooltip',
SCAN_TEST_TOOLTIP: 'scanTestTooltip',
WORKSPACE_EXPENSE_SUBMIT: 'workspaceExpenseSubmit',
},
SMART_BANNER_HEIGHT: 152,
TRAVEL: {
Expand Down
22 changes: 22 additions & 0 deletions src/components/ProductTrainingContext/TOOLTIPS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
LHN_WORKSPACE_CHAT_TOOLTIP,
GLOBAL_CREATE_TOOLTIP,
SCAN_TEST_TOOLTIP,
WORKSPACE_EXPENSE_SUBMIT,
} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES;

type ProductTrainingTooltipName = ValueOf<typeof CONST.PRODUCT_TRAINING_TOOLTIP_NAMES>;
Expand All @@ -27,6 +28,7 @@ type TooltipData = {
name: ProductTrainingTooltipName;
priority: number;
shouldShow: (props: ShouldShowConditionProps) => boolean;
isModalTooltip: boolean;
};

const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
Expand All @@ -39,6 +41,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: CONCEIRGE_LHN_GBR,
priority: 1300,
shouldShow: ({shouldUseNarrowLayout}) => !!shouldUseNarrowLayout,
isModalTooltip: false,
},
[RENAME_SAVED_SEARCH]: {
content: [
Expand All @@ -49,6 +52,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: RENAME_SAVED_SEARCH,
priority: 1250,
shouldShow: ({shouldUseNarrowLayout}) => !shouldUseNarrowLayout,
isModalTooltip: false,
},
[GLOBAL_CREATE_TOOLTIP]: {
content: [
Expand All @@ -60,6 +64,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: GLOBAL_CREATE_TOOLTIP,
priority: 1200,
shouldShow: () => true,
isModalTooltip: false,
},
[QUICK_ACTION_BUTTON]: {
content: [
Expand All @@ -70,6 +75,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: QUICK_ACTION_BUTTON,
priority: 1150,
shouldShow: () => true,
isModalTooltip: true,
},
[WORKSAPCE_CHAT_CREATE]: {
content: [
Expand All @@ -81,6 +87,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: WORKSAPCE_CHAT_CREATE,
priority: 1100,
shouldShow: () => true,
isModalTooltip: false,
},
[SEARCH_FILTER_BUTTON_TOOLTIP]: {
content: [
Expand All @@ -91,6 +98,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: SEARCH_FILTER_BUTTON_TOOLTIP,
priority: 1000,
shouldShow: () => true,
isModalTooltip: false,
},
[BOTTOM_NAV_INBOX_TOOLTIP]: {
content: [
Expand All @@ -102,6 +110,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: BOTTOM_NAV_INBOX_TOOLTIP,
priority: 900,
shouldShow: () => true,
isModalTooltip: false,
},
[LHN_WORKSPACE_CHAT_TOOLTIP]: {
content: [
Expand All @@ -113,6 +122,7 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: LHN_WORKSPACE_CHAT_TOOLTIP,
priority: 800,
shouldShow: () => true,
isModalTooltip: false,
},
[SCAN_TEST_TOOLTIP]: {
content: [
Expand All @@ -129,6 +139,18 @@ const TOOLTIPS: Record<ProductTrainingTooltipName, TooltipData> = {
name: SCAN_TEST_TOOLTIP,
priority: 900,
shouldShow: () => false,
isModalTooltip: false,
},
[WORKSPACE_EXPENSE_SUBMIT]: {
content: [
{text: 'productTrainingTooltip.workspaceExpenseSubmit.part1', isBold: true},
{text: 'productTrainingTooltip.workspaceExpenseSubmit.part2', isBold: false},
],
onHideTooltip: () => dismissProductTraining(WORKSPACE_EXPENSE_SUBMIT),
name: WORKSPACE_EXPENSE_SUBMIT,
priority: 200,
shouldShow: () => true,
isModalTooltip: true,
},
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/ProductTrainingContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ function ProductTrainingContextProvider({children}: ChildrenProps) {
return false;
}

// We need to make an exception for the QAB tooltip because it is shown in a modal, otherwise it would be hidden if a modal is visible
if (tooltipName !== CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.QUICK_ACTION_BUTTON && isModalVisible) {
// We need to make an exception if tooltip is shown in a modal, otherwise it would be hidden if a modal is visible
if (!tooltipConfig.isModalTooltip && isModalVisible) {
return false;
}

Expand Down
5 changes: 4 additions & 1 deletion src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function BaseSelectionList<TItem extends ListItem>(
listItemTitleStyles,
initialNumToRender = 12,
listItemTitleContainerStyles,
shouldShowEducationalTooltip = () => false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
shouldShowEducationalTooltip = () => false,
getShouldShowEducationalTooltip = () => false,

should we name it as getShouldShowEducationalTooltip? Atm, when I read the code, sometimes shouldShowEducationalTooltip is a func, sometimes it's a boolean.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not necessarily needed. I can rename it if needed but we can't use two verbs get or should together.

}: BaseSelectionListProps<TItem>,
ref: ForwardedRef<SelectionListHandle>,
) {
Expand Down Expand Up @@ -536,7 +537,8 @@ function BaseSelectionList<TItem extends ListItem>(
</>
);

const renderItem = ({item, index, section}: SectionListRenderItemInfo<TItem, SectionWithIndexOffset<TItem>>) => {
const renderItem = (data: SectionListRenderItemInfo<TItem, SectionWithIndexOffset<TItem>>) => {
const {item, index, section} = data;
const normalizedIndex = index + (section?.indexOffset ?? 0);
const isDisabled = !!section.isDisabled || item.isDisabled;
const isItemFocused = (!isDisabled || item.isSelected) && focusedIndex === normalizedIndex;
Expand Down Expand Up @@ -574,6 +576,7 @@ function BaseSelectionList<TItem extends ListItem>(
shouldHighlightSelectedItem={shouldHighlightSelectedItem}
singleExecution={singleExecution}
titleContainerStyles={listItemTitleContainerStyles}
shouldShowEducationalTooltip={shouldShowEducationalTooltip(data)}
/>
</View>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type BaseSelectionListItemRendererProps<TItem extends ListItem> = Omit<BaseListI
singleExecution: ReturnType<typeof useSingleExecution>['singleExecution'];
titleStyles?: StyleProp<TextStyle>;
titleContainerStyles?: StyleProp<ViewStyle>;
shouldShowEducationalTooltip?: boolean;
};

function BaseSelectionListItemRenderer<TItem extends ListItem>({
Expand Down Expand Up @@ -44,6 +45,7 @@ function BaseSelectionListItemRenderer<TItem extends ListItem>({
titleStyles,
singleExecution,
titleContainerStyles,
shouldShowEducationalTooltip,
}: BaseSelectionListItemRendererProps<TItem>) {
const handleOnCheckboxPress = () => {
if (isReportListItemType(item)) {
Expand Down Expand Up @@ -94,6 +96,7 @@ function BaseSelectionListItemRenderer<TItem extends ListItem>({
wrapperStyle={wrapperStyle}
titleStyles={titleStyles}
titleContainerStyles={titleContainerStyles}
shouldShowEducationalTooltip={shouldShowEducationalTooltip}
/>
{item.footerContent && item.footerContent}
</>
Expand Down
193 changes: 109 additions & 84 deletions src/components/SelectionList/InviteMemberListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import {View} from 'react-native';
import {FallbackAvatar} from '@components/Icon/Expensicons';
import MultipleAvatars from '@components/MultipleAvatars';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
import SelectCircle from '@components/SelectCircle';
import SubscriptAvatar from '@components/SubscriptAvatar';
import Text from '@components/Text';
import TextWithTooltip from '@components/TextWithTooltip';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {Icon} from '@src/types/onyx/OnyxCommon';
import BaseListItem from './BaseListItem';
Expand All @@ -37,6 +40,7 @@ function InviteMemberListItem<TItem extends ListItem>({
onFocus,
shouldSyncFocus,
shouldHighlightSelectedItem,
shouldShowEducationalTooltip = false,
}: InviteMemberListItemProps<TItem>) {
const styles = useThemeStyles();
const theme = useTheme();
Expand All @@ -49,6 +53,11 @@ function InviteMemberListItem<TItem extends ListItem>({

const shouldShowCheckBox = canSelectMultiple && !item.isDisabled;

const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSPACE_EXPENSE_SUBMIT,
shouldShowEducationalTooltip,
);

const handleCheckboxPress = useCallback(() => {
if (onCheckboxPress) {
onCheckboxPress(item);
Expand All @@ -58,92 +67,108 @@ function InviteMemberListItem<TItem extends ListItem>({
}, [item, onCheckboxPress, onSelectRow]);

return (
<BaseListItem
pressableStyle={[[shouldHighlightSelectedItem && item.isSelected && styles.activeComponentBG]]}
item={item}
wrapperStyle={[styles.flex1, styles.justifyContentBetween, styles.sidebarLinkInner, styles.userSelectNone, styles.peopleRow]}
isFocused={isFocused}
isDisabled={isDisabled}
showTooltip={showTooltip}
canSelectMultiple={canSelectMultiple}
onSelectRow={onSelectRow}
onDismissError={onDismissError}
rightHandSideComponent={rightHandSideComponent}
errors={item.errors}
pendingAction={item.pendingAction}
FooterComponent={
item.invitedSecondaryLogin ? (
<Text style={[styles.ml9, styles.ph5, styles.pb3, styles.textLabelSupporting]}>
{translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})}
</Text>
) : undefined
}
keyForList={item.keyForList}
onFocus={onFocus}
shouldSyncFocus={shouldSyncFocus}
shouldDisplayRBR={!shouldShowCheckBox}
<EducationalTooltip
shouldRender={shouldShowProductTrainingTooltip}
anchorAlignment={{
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
}}
shiftHorizontal={variables.workspaceLHNtooltipShiftHorizontal}
shiftVertical={variables.composerTooltipShiftVertical}
wrapperStyle={styles.productTrainingTooltipWrapper}
renderTooltipContent={renderProductTrainingTooltip}
onTooltipPress={hideProductTrainingTooltip}
>
{(hovered?: boolean) => (
<>
{!!item.icons &&
(item.shouldShowSubscript ? (
<SubscriptAvatar
mainAvatar={item.icons.at(0) ?? fallbackIcon}
secondaryAvatar={item.icons.at(1)}
showTooltip={showTooltip}
backgroundColor={hovered && !isFocused ? hoveredBackgroundColor : subscriptAvatarBorderColor}
/>
) : (
<MultipleAvatars
icons={item.icons}
shouldShowTooltip={showTooltip}
secondAvatarStyle={[
StyleUtils.getBackgroundAndBorderStyle(theme.sidebar),
isFocused ? StyleUtils.getBackgroundAndBorderStyle(focusedBackgroundColor) : undefined,
hovered && !isFocused ? StyleUtils.getBackgroundAndBorderStyle(hoveredBackgroundColor) : undefined,
]}
/>
))}
<View style={[styles.flex1, styles.flexColumn, styles.justifyContentCenter, styles.alignItemsStretch, styles.optionRow]}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<TextWithTooltip
shouldShowTooltip={showTooltip}
text={Str.removeSMSDomain(item.text ?? '')}
style={[
styles.optionDisplayName,
isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
item.isBold !== false && styles.sidebarLinkTextBold,
styles.pre,
item.alternateText ? styles.mb1 : null,
]}
/>
</View>
{!!item.alternateText && (
<TextWithTooltip
shouldShowTooltip={showTooltip}
text={Str.removeSMSDomain(item.alternateText ?? '')}
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
/>
)}
</View>
{!!item.rightElement && item.rightElement}
{!!shouldShowCheckBox && (
<PressableWithFeedback
onPress={handleCheckboxPress}
disabled={isDisabled}
role={CONST.ROLE.BUTTON}
accessibilityLabel={item.text ?? ''}
style={[styles.ml2, styles.optionSelectCircle]}
>
<SelectCircle
isChecked={item.isSelected ?? false}
selectCircleStyles={styles.ml0}
/>
</PressableWithFeedback>
<View>
Copy link
Contributor

Choose a reason for hiding this comment

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

Hi @parasharrajat is it necessary to wrap existing into a View component here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, otherwise tooltip does not render.

<BaseListItem
pressableStyle={[[shouldHighlightSelectedItem && item.isSelected && styles.activeComponentBG]]}
item={item}
wrapperStyle={[styles.flex1, styles.justifyContentBetween, styles.sidebarLinkInner, styles.userSelectNone, styles.peopleRow]}
isFocused={isFocused}
isDisabled={isDisabled}
showTooltip={showTooltip}
canSelectMultiple={canSelectMultiple}
onSelectRow={onSelectRow}
onDismissError={onDismissError}
rightHandSideComponent={rightHandSideComponent}
errors={item.errors}
pendingAction={item.pendingAction}
FooterComponent={
item.invitedSecondaryLogin ? (
<Text style={[styles.ml9, styles.ph5, styles.pb3, styles.textLabelSupporting]}>
{translate('workspace.people.invitedBySecondaryLogin', {secondaryLogin: item.invitedSecondaryLogin})}
</Text>
) : undefined
}
keyForList={item.keyForList}
onFocus={onFocus}
shouldSyncFocus={shouldSyncFocus}
shouldDisplayRBR={!shouldShowCheckBox}
>
{(hovered?: boolean) => (
<>
{!!item.icons &&
(item.shouldShowSubscript ? (
<SubscriptAvatar
mainAvatar={item.icons.at(0) ?? fallbackIcon}
secondaryAvatar={item.icons.at(1)}
showTooltip={showTooltip}
backgroundColor={hovered && !isFocused ? hoveredBackgroundColor : subscriptAvatarBorderColor}
/>
) : (
<MultipleAvatars
icons={item.icons}
shouldShowTooltip={showTooltip}
secondAvatarStyle={[
StyleUtils.getBackgroundAndBorderStyle(theme.sidebar),
isFocused ? StyleUtils.getBackgroundAndBorderStyle(focusedBackgroundColor) : undefined,
hovered && !isFocused ? StyleUtils.getBackgroundAndBorderStyle(hoveredBackgroundColor) : undefined,
]}
/>
))}

<View style={[styles.flex1, styles.flexColumn, styles.justifyContentCenter, styles.alignItemsStretch, styles.optionRow]}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<TextWithTooltip
shouldShowTooltip={showTooltip}
text={Str.removeSMSDomain(item.text ?? '')}
style={[
styles.optionDisplayName,
isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
item.isBold !== false && styles.sidebarLinkTextBold,
styles.pre,
item.alternateText ? styles.mb1 : null,
]}
/>
</View>
{!!item.alternateText && (
<TextWithTooltip
shouldShowTooltip={showTooltip}
text={Str.removeSMSDomain(item.alternateText ?? '')}
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
/>
)}
</View>
{!!item.rightElement && item.rightElement}
{!!shouldShowCheckBox && (
<PressableWithFeedback
onPress={handleCheckboxPress}
disabled={isDisabled}
role={CONST.ROLE.BUTTON}
accessibilityLabel={item.text ?? ''}
style={[styles.ml2, styles.optionSelectCircle]}
>
<SelectCircle
isChecked={item.isSelected ?? false}
selectCircleStyles={styles.ml0}
/>
</PressableWithFeedback>
)}
</>
)}
</>
)}
</BaseListItem>
</BaseListItem>
</View>
</EducationalTooltip>
);
}

Expand Down
Loading