Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 10 additions & 5 deletions src/components/ImportOnyxState/BaseImportOnyxState.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AttachmentPicker from '@components/AttachmentPicker';
import DecisionModal from '@components/DecisionModal';
import MenuItem from '@components/MenuItem';
import MenuItemSectionRow from '@components/MenuItem/presets/MenuItemSectionRow';

import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -37,16 +38,20 @@ function BaseImportOnyxState({
>
{({openPicker}) => {
return (
<MenuItem
icon={icons.Upload}
title={translate('initialSettingsPage.troubleshoot.importOnyxState')}
wrapperStyle={[styles.sectionMenuItemTopDescription]}
<MenuItemSectionRow
onPress={() => {
openPicker({
onPicked: (data) => onFileRead(data.at(0) ?? {}),
});
}}
/>
>
<MenuItem.Row>
<MenuItem.Icon src={icons.Upload} />
<MenuItem.Content>
<MenuItem.Title>{translate('initialSettingsPage.troubleshoot.importOnyxState')}</MenuItem.Title>
</MenuItem.Content>
</MenuItem.Row>
</MenuItemSectionRow>
);
}}
</AttachmentPicker>
Expand Down
10 changes: 9 additions & 1 deletion src/components/MenuItem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ import MenuItemRow from './layout/MenuItemRow';
import MenuItemTrailing from './layout/MenuItemTrailing';
import MenuItemIcon from './leaves/leading/MenuItemIcon';
import MenuItemDescription from './leaves/text/MenuItemDescription';
import MenuItemTitle from './leaves/text/MenuItemTitle';
import MenuItemTitle from './leaves/text/title/MenuItemTitle';
import MenuItemTitleBasic from './leaves/text/title/MenuItemTitleBasic';
import MenuItemTitlePlaceholder from './leaves/text/title/MenuItemTitlePlaceholder';
import MenuItemChevron from './leaves/trailing/MenuItemChevron';
import MenuItemErrorDot from './leaves/trailing/MenuItemErrorDot';
import MenuItemTrailingIcon from './leaves/trailing/MenuItemTrailingIcon';
import LegacyMenuItem from './MenuItem';

const MenuItem = Object.assign(LegacyMenuItem, {
Expand All @@ -41,8 +45,12 @@ const MenuItem = Object.assign(LegacyMenuItem, {
Trailing: MenuItemTrailing,
Icon: MenuItemIcon,
Title: MenuItemTitle,
TitleBasic: MenuItemTitleBasic,
TitlePlaceholder: MenuItemTitlePlaceholder,
Description: MenuItemDescription,
Chevron: MenuItemChevron,
ErrorDot: MenuItemErrorDot,
TrailingIcon: MenuItemTrailingIcon,
});

export default MenuItem;
Expand Down
1 change: 1 addition & 0 deletions src/components/MenuItem/layout/MenuItemRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ function MenuItemRoot({children, onPress, isDisabled = false, sentryLabel, acces
}

export default MenuItemRoot;
export type {MenuItemRootProps};
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,27 @@ import convertToLTR from '@libs/convertToLTR';

import CONST from '@src/CONST';

import type {StyleProp, TextStyle} from 'react-native';

import React from 'react';

type MenuItemTitleProps = {
/** Text to render as the title */
children: string | number;
import type MenuItemTitleProps from './types';

type BaseMenuItemTitleProps = MenuItemTitleProps & {
/** Typography layered on top of the shared title base — each leaf brings its own weight and colour */
style?: StyleProp<TextStyle>;
};

/** The title block of a `MenuItem.Content`. Bold, single line */
function MenuItemTitle({children}: MenuItemTitleProps) {
/** Everything the title leaves have in common — the shared type face, LTR handling and label registration */
function BaseMenuItemTitle({children, style}: BaseMenuItemTitleProps) {
const styles = useThemeStyles();
const {isDisabled, isInteractive} = useMenuItemConfig();

useMenuItemAccessibilityLabel('title', String(children));

return (
<Text
style={[styles.flexShrink1, styles.popoverMenuText, styles.textStrong, styles.pre, isInteractive && isDisabled && styles.userSelectNone, styles.ltr, styles.mw100]}
style={[styles.flexShrink1, styles.popoverMenuText, styles.pre, isInteractive && isDisabled && styles.userSelectNone, styles.ltr, styles.mw100, style]}
numberOfLines={1}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: isInteractive && isDisabled}}
>
Expand All @@ -33,4 +37,4 @@ function MenuItemTitle({children}: MenuItemTitleProps) {
);
}

export default MenuItemTitle;
export default BaseMenuItemTitle;
16 changes: 16 additions & 0 deletions src/components/MenuItem/leaves/text/title/MenuItemTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import useThemeStyles from '@hooks/useThemeStyles';

import React from 'react';

import type MenuItemTitleProps from './types';

import BaseMenuItemTitle from './BaseMenuItemTitle';

/** The title block of a `MenuItem.Content`. Bold, single line */
function MenuItemTitle({children}: MenuItemTitleProps) {
const styles = useThemeStyles();

return <BaseMenuItemTitle style={styles.textStrong}>{children}</BaseMenuItemTitle>;
}

export default MenuItemTitle;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import type MenuItemTitleProps from './types';

import BaseMenuItemTitle from './BaseMenuItemTitle';

/**
* The title of a `MenuItem.Content` at regular weight, for rows that pair a label with the value
* it holds — there the bold of `MenuItem.Title` would compete with the value for attention.
*/
function MenuItemTitleBasic({children}: MenuItemTitleProps) {
return <BaseMenuItemTitle>{children}</BaseMenuItemTitle>;
}

export default MenuItemTitleBasic;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import useThemeStyles from '@hooks/useThemeStyles';

import React from 'react';

import type MenuItemTitleProps from './types';

import BaseMenuItemTitle from './BaseMenuItemTitle';

/**
* The title of a `MenuItem.Content` for a row whose value has not been set yet. Muted and regular
* weight, so it reads as a prompt rather than as content the way `MenuItem.Title` does.
*/
function MenuItemTitlePlaceholder({children}: MenuItemTitleProps) {
const styles = useThemeStyles();

return <BaseMenuItemTitle style={styles.colorMuted}>{children}</BaseMenuItemTitle>;
}

export default MenuItemTitlePlaceholder;
7 changes: 7 additions & 0 deletions src/components/MenuItem/leaves/text/title/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Props shared by every title leaf of a `MenuItem.Content` */
type MenuItemTitleProps = {
/** Text to render as the title */
children: string | number;
};

export default MenuItemTitleProps;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Icon from '@components/Icon';
import {useMenuItemConfig, useMenuItemInteraction} from '@components/MenuItem/MenuItemContext';

import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';

import getButtonState from '@libs/getButtonState';

import variables from '@styles/variables';

import type IconAsset from '@src/types/utils/IconAsset';

import React from 'react';
import {View} from 'react-native';

type MenuItemTrailingIconProps = {
/** Icon to display */
src: IconAsset;
};

/**
* A trailing icon of a `MenuItem.Row` that is not a navigation indicator — an "opens in a new tab"
* arrow, for instance. Unlike `MenuItem.Chevron` it stays at full opacity when the row is not
* hovered, since it carries meaning on its own.
*/
function MenuItemTrailingIcon({src}: MenuItemTrailingIconProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isDisabled, isInteractive} = useMenuItemConfig();
const {isHovered, isPressed} = useMenuItemInteraction();

return (
<View style={[styles.menuItemChevron, isDisabled && styles.cursorDisabled]}>
<Icon
src={src}
fill={StyleUtils.getIconFillColor(getButtonState(isHovered, isPressed, false, isDisabled, isInteractive))}
width={variables.iconSizeNormal}
height={variables.iconSizeNormal}
/>
</View>
);
}

export default MenuItemTrailingIcon;
2 changes: 1 addition & 1 deletion src/components/MenuItem/presets/MenuItemAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import MenuItemContent from '@components/MenuItem/layout/MenuItemContent';
import MenuItemRoot from '@components/MenuItem/layout/MenuItemRoot';
import MenuItemRow from '@components/MenuItem/layout/MenuItemRow';
import MenuItemIcon from '@components/MenuItem/leaves/leading/MenuItemIcon';
import MenuItemTitle from '@components/MenuItem/leaves/text/MenuItemTitle';
import MenuItemTitle from '@components/MenuItem/leaves/text/title/MenuItemTitle';

import {callFunctionIfActionIsAllowed} from '@userActions/Session';

Expand Down
2 changes: 1 addition & 1 deletion src/components/MenuItem/presets/MenuItemNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MenuItemRoot from '@components/MenuItem/layout/MenuItemRoot';
import MenuItemRow from '@components/MenuItem/layout/MenuItemRow';
import MenuItemTrailing from '@components/MenuItem/layout/MenuItemTrailing';
import MenuItemIcon from '@components/MenuItem/leaves/leading/MenuItemIcon';
import MenuItemTitle from '@components/MenuItem/leaves/text/MenuItemTitle';
import MenuItemTitle from '@components/MenuItem/leaves/text/title/MenuItemTitle';
import MenuItemChevron from '@components/MenuItem/leaves/trailing/MenuItemChevron';

import {callFunctionIfActionIsAllowed} from '@userActions/Session';
Expand Down
42 changes: 42 additions & 0 deletions src/components/MenuItem/presets/MenuItemSectionRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import MenuItemRoot from '@components/MenuItem/layout/MenuItemRoot';
import type {MenuItemRootProps} from '@components/MenuItem/layout/MenuItemRoot';

import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';

import {callFunctionIfActionIsAllowed} from '@userActions/Session';

import type {RefObject} from 'react';

import React from 'react';
import {View} from 'react-native';

type MenuItemSectionRowProps = MenuItemRootProps & {
/** Ref to the bleed wrapper. Use it when a popover needs to anchor on the row. */
ref?: RefObject<View | null>;
};

/** A `MenuItem.Root` that spans the full width of a `Section`, ignoring the section's own horizontal padding */
function MenuItemSectionRow({children, onPress, isDisabled = false, sentryLabel, accessibilityLabel, ref}: MenuItemSectionRowProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();

return (
<View
ref={ref}
style={shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8}
>
<MenuItemRoot
onPress={callFunctionIfActionIsAllowed(onPress)}
isDisabled={isDisabled}
sentryLabel={sentryLabel}
accessibilityLabel={accessibilityLabel}
>
<View style={!shouldUseNarrowLayout && styles.ph3}>{children}</View>
</MenuItemRoot>
</View>
);
}

export default MenuItemSectionRow;
export type {MenuItemSectionRowProps};
19 changes: 13 additions & 6 deletions src/pages/settings/Copilot/CopilotPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider';
import {useLockedAccountActions, useLockedAccountState} from '@components/LockedAccountModalProvider';
import MenuItem from '@components/MenuItem';
import type {MenuItemProps} from '@components/MenuItem';
import MenuItemSectionRow from '@components/MenuItem/presets/MenuItemSectionRow';
import MenuItemList from '@components/MenuItemList';
import {ModalActions} from '@components/Modal/Global/ModalContext';
import PopoverMenu from '@components/PopoverMenu';
Expand Down Expand Up @@ -491,9 +492,7 @@ function CopilotPage() {
</>
)}
{isAgentAccount === false ? (
<MenuItem
title={translate('delegate.addCopilot')}
icon={icons.UserPlus}
<MenuItemSectionRow
sentryLabel={CONST.SENTRY_LABEL.SETTINGS_SECURITY.ADD_COPILOT}
onPress={() => {
if (isActingAsDelegate) {
Expand All @@ -510,9 +509,17 @@ function CopilotPage() {
}
Navigation.navigate(ROUTES.SETTINGS_ADD_DELEGATE);
}}
shouldShowRightIcon
wrapperStyle={[styles.sectionMenuItemTopDescription]}
/>
>
<MenuItem.Row>
<MenuItem.Icon src={icons.UserPlus} />
<MenuItem.Content>
<MenuItem.Title>{translate('delegate.addCopilot')}</MenuItem.Title>
</MenuItem.Content>
<MenuItem.Trailing>
<MenuItem.Chevron />
</MenuItem.Trailing>
</MenuItem.Row>
</MenuItemSectionRow>
) : null}
</Section>
<PopoverMenu
Expand Down
19 changes: 10 additions & 9 deletions src/pages/settings/DynamicKeyboardShortcutsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ function DynamicKeyboardShortcutsPage() {
.filter((shortcut): shortcut is Shortcut => !!shortcut.descriptionKey);

const renderShortcut = (shortcut: Shortcut) => (
<MenuItem
key={shortcut.displayName}
title={shortcut.displayName}
description={translate(`keyboardShortcutsPage.shortcuts.${shortcut.descriptionKey}`)}
wrapperStyle={[styles.ph0, styles.cursorAuto]}
interactive={false}
/>
<MenuItem.Root key={shortcut.displayName}>
<MenuItem.Row>
<MenuItem.Content>
<MenuItem.Title>{shortcut.displayName}</MenuItem.Title>
<MenuItem.Description>{translate(`keyboardShortcutsPage.shortcuts.${shortcut.descriptionKey}`)}</MenuItem.Description>
</MenuItem.Content>
</MenuItem.Row>
</MenuItem.Root>
);

return (
Expand All @@ -57,8 +58,8 @@ function DynamicKeyboardShortcutsPage() {
onBackButtonPress={() => Navigation.goBack(backPath)}
/>
<ScrollView contentContainerStyle={styles.flexGrow1}>
<View style={[styles.ph5, styles.pv3]}>
<Text style={[styles.mb3, styles.webViewStyles.baseFontStyle]}>{translate('keyboardShortcutsPage.subtitle')}</Text>
<View style={styles.pv3}>
<Text style={[styles.ph5, styles.mb3, styles.webViewStyles.baseFontStyle]}>{translate('keyboardShortcutsPage.subtitle')}</Text>
{shortcuts.map(renderShortcut)}
</View>
</ScrollView>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';
import MenuItem from '@components/MenuItem';
import MenuItemSectionRow from '@components/MenuItem/presets/MenuItemSectionRow';

import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';

import Navigation from '@libs/Navigation/Navigation';

Expand All @@ -15,7 +15,6 @@ import React from 'react';
function CancelSubscriptionMenuItem() {
const icons = useMemoizedLazyExpensifyIcons(['CircleSlash']);
const {translate} = useLocalize();
const styles = useThemeStyles();
const {isActingAsDelegate} = useDelegateNoAccessState();
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();

Expand All @@ -27,15 +26,20 @@ function CancelSubscriptionMenuItem() {
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_CANCEL_SUBSCRIPTION);
};
return (
<MenuItem
title={translate('subscription.cancelSubscription.title')}
icon={icons.CircleSlash}
shouldShowRightIcon
wrapperStyle={styles.sectionMenuItemTopDescription}
titleStyle={styles.textStrong}
<MenuItemSectionRow
onPress={handleCancelSubscriptionPress}
sentryLabel={CONST.SENTRY_LABEL.SETTINGS_SUBSCRIPTION.CANCEL_SUBSCRIPTION}
/>
>
<MenuItem.Row>
<MenuItem.Icon src={icons.CircleSlash} />
<MenuItem.Content>
<MenuItem.Title>{translate('subscription.cancelSubscription.title')}</MenuItem.Title>
</MenuItem.Content>
<MenuItem.Trailing>
<MenuItem.Chevron />
</MenuItem.Trailing>
</MenuItem.Row>
</MenuItemSectionRow>
);
}

Expand Down
Loading
Loading