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

Feature/35713 bulk actions #37199

Merged
merged 59 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
bc020b1
button with dropdown menu refactor
burczu Feb 23, 2024
f8bcd09
button with dropdown refactor simplified
burczu Feb 23, 2024
6b01648
showing button with dropdown menu when item selected
burczu Feb 23, 2024
aab0b73
Merge branch 'main' into feature/35713-bulk-actions
burczu Feb 23, 2024
8d96108
text and style of the dropdown button adjusted for the workspace memb…
burczu Feb 26, 2024
a1c7b3c
custom onSelect action handling by ButtonWithDropdown added
burczu Feb 26, 2024
a811d75
Merge branch 'main' into feature/35713-bulk-actions
burczu Feb 26, 2024
7480bc0
remove members bulk action handled
burczu Feb 26, 2024
ce0a424
offline deletion issue (not striking out member) fixed
burczu Feb 26, 2024
7afd58c
handling style prop injected by the OfflineWithFeedback component
burczu Feb 26, 2024
9a6be7a
line through on native issue fixed
burczu Feb 26, 2024
65de55f
always show drop down menu prop renamed
burczu Feb 27, 2024
9afe006
switched to use variables for icon sizing
burczu Feb 27, 2024
bdddc57
building bulk actions options conditional fixed
burczu Feb 27, 2024
9bab577
missing import added
burczu Feb 27, 2024
8440181
using dedicated method for all the bulk actions added
burczu Feb 27, 2024
a4c37a2
lint and prettier changes
burczu Feb 27, 2024
335aa49
on selected definition fixed
burczu Feb 27, 2024
6440417
updating workspace member's role added
burczu Feb 27, 2024
4ab2e7a
Merge branch 'main' into feature/35713-bulk-actions
burczu Feb 27, 2024
632b3a5
type definition fixed
burczu Feb 27, 2024
0a560d5
prettier fix
burczu Feb 27, 2024
55ded25
missing API types added
burczu Feb 27, 2024
631e8c1
unnecessary comment removed
burczu Feb 27, 2024
70988c3
renamings
burczu Feb 27, 2024
6ad10d8
sub-header spacing adjusted
burczu Feb 27, 2024
563f835
workspace members role data type extracted
burczu Feb 27, 2024
8d62e93
unnecessary parenthesis removed
burczu Feb 27, 2024
7ffa14f
prettier, one more time
burczu Feb 27, 2024
435ce36
Merge branch 'main' into feature/35713-bulk-actions
burczu Feb 28, 2024
ea05d00
variable names adjusted
burczu Feb 28, 2024
debb91b
additional space for readability
burczu Feb 28, 2024
4289987
switched to reduce instead map + filter
burczu Feb 28, 2024
ea7df2d
new line added
burczu Feb 28, 2024
6a34a5f
prettier
burczu Feb 28, 2024
93000ab
using cursor default if the button is disabled because of custom text
burczu Feb 28, 2024
7257a0f
selected background adjusted
burczu Feb 28, 2024
cedc07f
button with dropdown style adjustments
burczu Feb 28, 2024
91c8f0a
hovering split button options icon colors fixed
burczu Feb 28, 2024
9da8e79
offline removing issue fixed
burczu Feb 28, 2024
6d11560
icons updated
burczu Feb 28, 2024
b58d23c
Merge branch 'main' into feature/35713-bulk-actions
burczu Feb 28, 2024
212014e
cleanup after icon update
burczu Feb 28, 2024
255d895
linting fixes
burczu Feb 28, 2024
226270d
Merge branch 'main' into feature/35713-bulk-actions
burczu Feb 28, 2024
ab0d2f4
Merge branch 'main' into feature/35713-bulk-actions
burczu Feb 29, 2024
dffefd6
unnecessary style removed
burczu Feb 29, 2024
bcd44e9
members column label moved left
burczu Feb 29, 2024
2229ce2
Merge branch 'main' into feature/35713-bulk-actions
burczu Mar 1, 2024
dc8b825
typecheck fix
burczu Mar 1, 2024
1a23c3d
badge border when selected adjusted
burczu Mar 1, 2024
78de600
type simplified
burczu Mar 1, 2024
5552628
prettier changes
burczu Mar 1, 2024
7bc882c
missing disabling the checkbox if the row is disabled
burczu Mar 1, 2024
a3f70d8
button with dropdown with one element fixed
burczu Mar 1, 2024
5d4509c
Merge branch 'main' into feature/35713-bulk-actions
burczu Mar 1, 2024
0060083
quick fix
burczu Mar 1, 2024
1ad8d37
quick fix
burczu Mar 1, 2024
c151114
resolve conflicts
luacmartins Mar 1, 2024
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
12 changes: 12 additions & 0 deletions assets/images/make-admin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions assets/images/remove-members.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,11 @@ const CONST = {
},
ID_FAKE: '_FAKE_',
EMPTY: 'EMPTY',
MEMBERS_BULK_ACTION_TYPES: {
burczu marked this conversation as resolved.
Show resolved Hide resolved
REMOVE: 'remove',
MAKE_MEMBER: 'makeMember',
MAKE_ADMIN: 'makeAdmin',
},
},

CUSTOM_UNITS: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ function Button(
large ? styles.buttonLarge : undefined,
success ? styles.buttonSuccess : undefined,
danger ? styles.buttonDanger : undefined,
isDisabled && (success || danger) ? styles.buttonOpacityDisabled : undefined,
isDisabled ? styles.buttonOpacityDisabled : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

As this is general component, let's make sure that this doesn't cause any minor UI regression. Especially for the case of isDisabled = true, danger = true

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was asked by @shawnborton to adjust the buttons styles according to the most recent design (not sure if you have access to it) - to cut the story short no matter in which state is the button (default, success or danger) we should lower its opacity by 50% in case it is disabled. I don't think this change would have huge impact on the App functionality - I've clicked through the app and it seems everything is ok. Do you want something more from me here?

isDisabled && !danger && !success ? styles.buttonDisabled : undefined,
shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined,
shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,77 +1,26 @@
import type {RefObject} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import type {ValueOf} from 'type-fest';
import Button from '@components/Button';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import PopoverMenu from '@components/PopoverMenu';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import type {AnchorPosition} from '@styles/index';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import PopoverMenu from './PopoverMenu';
import type {AnchorPosition} from '@src/styles';
import type {ButtonWithDropdownMenuProps} from './types';

type PaymentType = DeepValueOf<typeof CONST.IOU.PAYMENT_TYPE | typeof CONST.IOU.REPORT_ACTION_TYPE>;

type DropdownOption = {
value: PaymentType;
text: string;
icon: IconAsset;
iconWidth?: number;
iconHeight?: number;
iconDescription?: string;
};

type ButtonWithDropdownMenuProps = {
/** Text to display for the menu header */
menuHeaderText?: string;

/** Callback to execute when the main button is pressed */
onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: PaymentType) => void;

/** Callback to execute when a dropdown option is selected */
onOptionSelected?: (option: DropdownOption) => void;

/** Call the onPress function on main button when Enter key is pressed */
pressOnEnter?: boolean;

/** Whether we should show a loading state for the main button */
isLoading?: boolean;

/** The size of button size */
buttonSize: ValueOf<typeof CONST.DROPDOWN_BUTTON_SIZE>;

/** Should the confirmation button be disabled? */
isDisabled?: boolean;

/** Additional styles to add to the component */
style?: StyleProp<ViewStyle>;

/** Menu options to display */
/** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */
options: DropdownOption[];

/** The anchor alignment of the popover menu */
anchorAlignment?: AnchorAlignment;

/* ref for the button */
buttonRef: RefObject<View>;

/** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */
enterKeyEventListenerPriority?: number;
};

function ButtonWithDropdownMenu({
function ButtonWithDropdownMenu<IValueType>({
success = false,
isLoading = false,
isDisabled = false,
pressOnEnter = false,
shouldAlwaysShowDropdownMenu = false,
menuHeaderText = '',
customText,
style,
buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
anchorAlignment = {
Expand All @@ -83,7 +32,7 @@ function ButtonWithDropdownMenu({
options,
onOptionSelected,
enterKeyEventListenerPriority = 0,
}: ButtonWithDropdownMenuProps) {
}: ButtonWithDropdownMenuProps<IValueType>) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -118,27 +67,27 @@ function ButtonWithDropdownMenu({

return (
<View>
{options.length > 1 ? (
{shouldAlwaysShowDropdownMenu || options.length > 1 ? (
<View style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, style]}>
<Button
success
success={success}
pressOnEnter={pressOnEnter}
ref={buttonRef}
onPress={(event) => onPress(event, selectedItem.value)}
text={selectedItem.text}
text={customText ?? selectedItem.text}
isDisabled={isDisabled}
isLoading={isLoading}
shouldRemoveRightBorderRadius
style={[styles.flex1, styles.pr0]}
large={isButtonSizeLarge}
medium={!isButtonSizeLarge}
innerStyles={[innerStyleDropButton]}
innerStyles={[innerStyleDropButton, customText !== undefined && styles.cursorDefault, customText !== undefined && styles.pointerEventsNone]}
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>

<Button
ref={caretButton}
success
success={success}
isDisabled={isDisabled}
style={[styles.pl0]}
onPress={() => setIsMenuVisible(!isMenuVisible)}
Expand All @@ -149,19 +98,21 @@ function ButtonWithDropdownMenu({
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
>
<View style={[styles.dropDownButtonCartIconView, innerStyleDropButton]}>
<View style={[styles.buttonDivider]} />
<View style={[success ? styles.buttonSuccessDivider : styles.buttonDivider]} />
<View style={[styles.dropDownButtonArrowContain]}>
<Icon
src={Expensicons.DownArrow}
fill={theme.textLight}
fill={success ? theme.buttonSuccessText : theme.icon}
width={variables.iconSizeSmall}
height={variables.iconSizeSmall}
/>
</View>
</View>
</Button>
</View>
) : (
<Button
success
success={success}
ref={buttonRef}
pressOnEnter={pressOnEnter}
isDisabled={isDisabled}
Expand All @@ -175,7 +126,7 @@ function ButtonWithDropdownMenu({
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
)}
{options.length > 1 && popoverAnchorPosition && (
{(shouldAlwaysShowDropdownMenu || options.length > 1) && popoverAnchorPosition && (
<PopoverMenu
isVisible={isMenuVisible}
onClose={() => setIsMenuVisible(false)}
Expand All @@ -187,10 +138,12 @@ function ButtonWithDropdownMenu({
headerText={menuHeaderText}
menuItems={options.map((item, index) => ({
...item,
onSelected: () => {
onOptionSelected?.(item);
setSelectedItemIndex(index);
},
onSelected:
item.onSelected ??
(() => {
onOptionSelected?.(item);
setSelectedItemIndex(index);
}),
}))}
/>
)}
Expand All @@ -200,4 +153,4 @@ function ButtonWithDropdownMenu({

ButtonWithDropdownMenu.displayName = 'ButtonWithDropdownMenu';

export default React.memo(ButtonWithDropdownMenu);
export default ButtonWithDropdownMenu;
71 changes: 71 additions & 0 deletions src/components/ButtonWithDropdownMenu/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type {RefObject} from 'react';
import type {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native';
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import type IconAsset from '@src/types/utils/IconAsset';

type PaymentType = DeepValueOf<typeof CONST.IOU.PAYMENT_TYPE | typeof CONST.IOU.REPORT_ACTION_TYPE>;

type WorkspaceMemberBulkActionType = DeepValueOf<typeof CONST.POLICY.MEMBERS_BULK_ACTION_TYPES>;

type DropdownOption<TValueType> = {
value: TValueType;
text: string;
icon: IconAsset;
iconWidth?: number;
iconHeight?: number;
iconDescription?: string;
onSelected?: () => void;
};

type ButtonWithDropdownMenuProps<TValueType> = {
/** The custom text to display on the main button instead of selected option */
customText?: string;

/** Text to display for the menu header */
menuHeaderText?: string;

/** Callback to execute when the main button is pressed */
onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: TValueType) => void;

/** Callback to execute when a dropdown option is selected */
onOptionSelected?: (option: DropdownOption<TValueType>) => void;

/** Call the onPress function on main button when Enter key is pressed */
pressOnEnter?: boolean;

/** Whether we should show a loading state for the main button */
isLoading?: boolean;

/** The size of button size */
buttonSize: ValueOf<typeof CONST.DROPDOWN_BUTTON_SIZE>;

/** Should the confirmation button be disabled? */
isDisabled?: boolean;

/** Additional styles to add to the component */
style?: StyleProp<ViewStyle>;

/** Menu options to display */
/** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */
options: Array<DropdownOption<TValueType>>;

/** The anchor alignment of the popover menu */
anchorAlignment?: AnchorAlignment;

/* ref for the button */
buttonRef: RefObject<View>;

/** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */
enterKeyEventListenerPriority?: number;

/** Whether the button should use success style or not */
success?: boolean;

/** Whether the dropdown menu should be shown even if it has only one option */
shouldAlwaysShowDropdownMenu?: boolean;
};

export type {PaymentType, WorkspaceMemberBulkActionType, DropdownOption, ButtonWithDropdownMenuProps};
4 changes: 4 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import Lock from '@assets/images/lock.svg';
import Luggage from '@assets/images/luggage.svg';
import MagnifyingGlass from '@assets/images/magnifying-glass.svg';
import Mail from '@assets/images/mail.svg';
import MakeAdmin from '@assets/images/make-admin.svg';
import Megaphone from '@assets/images/megaphone.svg';
import Menu from '@assets/images/menu.svg';
import Meter from '@assets/images/meter.svg';
Expand All @@ -118,6 +119,7 @@ import QrCode from '@assets/images/qrcode.svg';
import QuestionMark from '@assets/images/question-mark-circle.svg';
import ReceiptSearch from '@assets/images/receipt-search.svg';
import Receipt from '@assets/images/receipt.svg';
import RemoveMembers from '@assets/images/remove-members.svg';
import Rotate from '@assets/images/rotate-image.svg';
import RotateLeft from '@assets/images/rotate-left.svg';
import Scan from '@assets/images/scan.svg';
Expand Down Expand Up @@ -242,6 +244,7 @@ export {
Luggage,
MagnifyingGlass,
Mail,
MakeAdmin,
Menu,
Meter,
Megaphone,
Expand Down Expand Up @@ -269,6 +272,7 @@ export {
QrCode,
QuestionMark,
Receipt,
RemoveMembers,
ReceiptSearch,
Rotate,
RotateLeft,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
/>
) : (
<ButtonWithDropdownMenu
success
pressOnEnter
isDisabled={shouldDisableButton}
onPress={(_event, value) => confirm(value)}
Expand Down
Loading
Loading