Skip to content
Open
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
21 changes: 6 additions & 15 deletions src/components/BulkActionBar/useBulkActionBarFocus/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import getHadTabNavigation from '@libs/hadTabNavigation';
import isHTMLElement from '@libs/isHTMLElement';
import {pickLauncher} from '@libs/LauncherStack';
import {nominateTriggerFallback} from '@libs/NavigationFocusReturn';
Expand All @@ -8,18 +9,6 @@ import {useEffect, useLayoutEffect, useRef, useState} from 'react';

import type UseBulkActionBarFocus from './types';

/**
* Whether the browser is already drawing a focus ring on this element. Browsers that cannot be asked are assumed to be
* drawing one, so a ring is never taken away from someone who navigated there with the keyboard.
*/
function isShowingFocusRing(element: HTMLElement): boolean {
try {
return element.matches(':focus-visible');
} catch {
return true;
}
}

/** Tracks where focus is relative to the bar, and hands it back when the bar goes away holding it. */
const useBulkActionBarFocus: UseBulkActionBarFocus = (barRef, isCoveredByScreen) => {
// Where focus was before the bar took it. The bar goes away with the selection, so focus has to be handed back.
Expand Down Expand Up @@ -141,9 +130,11 @@ const useBulkActionBarFocus: UseBulkActionBarFocus = (barRef, isCoveredByScreen)
return;
}

// Esc makes whatever holds focus match `:focus-visible`, so a checkbox clicked with the mouse would light up
// with a focus ring just as the selection it belongs to disappears.
if (isShowingFocusRing(active)) {
// Esc is itself what makes whatever holds focus match `:focus-visible`, and the browser has already applied
// that by the time this runs, so the element cannot be asked whether it had a ring beforehand. How the focus
// arrived is the answer that survives. A ring belongs to someone who tabbed there, not to someone who clicked
// the checkbox and is now watching it light up as the selection it belongs to disappears.
if (getHadTabNavigation()) {
return;
}

Expand Down
7 changes: 7 additions & 0 deletions src/components/Tables/AgentsTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import type {EmptyStateButton} from '@components/EmptyStateComponent/types';
import RenderHTML from '@components/RenderHTML';
import type {CompareItemsCallback, IsItemInSearchCallback, TableColumn, TableData, TableHandle} from '@components/Table';
Expand All @@ -18,6 +19,7 @@ import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {AgentOwnerType} from '@src/types/onyx/Agent';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type DeepValueOf from '@src/types/utils/DeepValueOf';

import type {ListRenderItemInfo} from '@shopify/flash-list';
import type {ValueOf} from 'type-fest';
Expand Down Expand Up @@ -79,6 +81,9 @@ type AgentsTableProps = {
shouldShowOwnerColumn?: boolean;

onRowSelectionChange: (selectedRowKeys: string[]) => void;

/** The actions the selected agents support, offered in the floating bar the wide layout shows while rows are selected. */
bulkActionOptions?: Array<DropdownOption<DeepValueOf<typeof CONST.AGENTS.BULK_ACTION_TYPES>>>;
};

export default function AgentsTable({
Expand All @@ -91,6 +96,7 @@ export default function AgentsTable({
shouldShowRoleColumn,
shouldShowOwnerColumn,
onRowSelectionChange,
bulkActionOptions,
}: AgentsTableProps) {
const styles = useThemeStyles();
const {translate, localeCompare} = useLocalize();
Expand Down Expand Up @@ -213,6 +219,7 @@ export default function AgentsTable({
<Table.NoResultsState />
<Table.Header />
<Table.Body />
{!!bulkActionOptions && <Table.BulkActionBar options={bulkActionOptions} />}
</Table>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/components/Tables/DomainMembersTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {DomainMemberBulkActionType, DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import type {CompareItemsCallback, FilterConfig, IsItemInFilterCallback, IsItemInSearchCallback, TableColumn, TableData, TableHandle} from '@components/Table';
import Table, {composeTableListHeader} from '@components/Table';
import {useTableContext} from '@components/Table/TableContext';
Expand Down Expand Up @@ -43,6 +44,9 @@ type DomainMembersTableProps = {
filterConfig?: FilterConfig<DomainMembersTableFilterKey>;
isItemInFilter?: IsItemInFilterCallback<DomainMemberRowData>;
headerComponent?: React.ReactElement;

/** The actions the selected members support, offered in the floating bar the wide layout shows while rows are selected. */
bulkActionOptions?: Array<DropdownOption<DomainMemberBulkActionType>>;
};

/**
Expand Down Expand Up @@ -82,6 +86,7 @@ export default function DomainMembersTable({
filterConfig,
isItemInFilter,
headerComponent,
bulkActionOptions,
}: DomainMembersTableProps) {
const {translate, localeCompare} = useLocalize();
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
Expand Down Expand Up @@ -171,6 +176,7 @@ export default function DomainMembersTable({
<Table.NoResultsState />
<Table.Header />
<Table.Body />
{!!bulkActionOptions && <Table.BulkActionBar options={bulkActionOptions} />}
</Table>
);
}
Expand Down
8 changes: 7 additions & 1 deletion src/components/Tables/PersonalExpenseRulesTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import type {CompareItemsCallback, IsItemInSearchCallback, TableColumn, TableData} from '@components/Table';
import Table, {composeTableListHeader} from '@components/Table';

Expand All @@ -14,6 +15,7 @@ import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';
import type DeepValueOf from '@src/types/utils/DeepValueOf';

import type {ListRenderItemInfo} from '@shopify/flash-list';

Expand All @@ -37,9 +39,12 @@ type PersonalExpenseRulesTableProps = {
personalExpenseRules: PersonalExpenseRuleRowData[];
selectedKeys: string[];
onRowSelectionChange: (selectedRowKeys: string[]) => void;

/** The actions the selected rules support, offered in the floating bar the wide layout shows while rows are selected. */
bulkActionOptions?: Array<DropdownOption<DeepValueOf<typeof CONST.EXPENSE_RULES.BULK_ACTION_TYPES>>>;
};

export default function PersonalExpenseRulesTable({headerComponent, personalExpenseRules, selectedKeys, onRowSelectionChange}: PersonalExpenseRulesTableProps) {
export default function PersonalExpenseRulesTable({headerComponent, personalExpenseRules, selectedKeys, onRowSelectionChange, bulkActionOptions}: PersonalExpenseRulesTableProps) {
const {translate, localeCompare} = useLocalize();
const icons = useMemoizedLazyExpensifyIcons(['Plus']);
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
Expand Down Expand Up @@ -129,6 +134,7 @@ export default function PersonalExpenseRulesTable({headerComponent, personalExpe
<Table.NoResultsState />
<Table.Header />
<Table.Body />
{!!bulkActionOptions && <Table.BulkActionBar options={bulkActionOptions} />}
</Table>
);
}
Expand Down
8 changes: 8 additions & 0 deletions src/components/Tables/WorkspaceCategoriesTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import type {EmptyStateButton} from '@components/EmptyStateComponent/types';
import type {CompareItemsCallback, IsItemInSearchCallback, TableColumn, TableData, TableHandle} from '@components/Table';
import Table, {composeTableListHeader} from '@components/Table';
Expand All @@ -11,7 +12,9 @@ import type {AvatarSource} from '@libs/UserAvatarUtils';

import variables from '@styles/variables';

import type CONST from '@src/CONST';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type DeepValueOf from '@src/types/utils/DeepValueOf';

import type {ListRenderItemInfo} from '@shopify/flash-list';

Expand Down Expand Up @@ -50,6 +53,9 @@ type WorkspaceCategoriesTableProps = {
emptyStateButtons: EmptyStateButton[] | undefined;
onRowSelectionChange: (selectedRowKeys: string[]) => void;
headerComponent?: React.ReactElement;

/** The actions the selected categories support, offered in the floating bar the wide layout shows while rows are selected. */
bulkActionOptions?: Array<DropdownOption<DeepValueOf<typeof CONST.POLICY.BULK_ACTION_TYPES>>>;
};

export default function WorkspaceCategoriesTable({
Expand All @@ -63,6 +69,7 @@ export default function WorkspaceCategoriesTable({
emptyStateButtons,
onRowSelectionChange,
headerComponent,
bulkActionOptions,
}: WorkspaceCategoriesTableProps) {
const styles = useThemeStyles();
const {translate, localeCompare} = useLocalize();
Expand Down Expand Up @@ -181,6 +188,7 @@ export default function WorkspaceCategoriesTable({
<Table.NoResultsState />
<Table.Header />
<Table.Body />
{!!bulkActionOptions && <Table.BulkActionBar options={bulkActionOptions} />}
</Table>
);
}
Expand Down
9 changes: 9 additions & 0 deletions src/components/Tables/WorkspaceCategoryRulesTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import Table, {composeTableListHeader} from '@components/Table';
import type {CompareItemsCallback, IsItemInSearchCallback, TableColumn, TableData, TableRenderRowProps} from '@components/Table';
import type {TableEmptyStateProps} from '@components/Table/TableEmptyStates/TableEmptyState';
Expand All @@ -10,6 +11,9 @@ import tokenizedSearch from '@libs/tokenizedSearch';

import variables from '@styles/variables';

import type CONST from '@src/CONST';
import type DeepValueOf from '@src/types/utils/DeepValueOf';

import type {ListRenderItemInfo} from '@shopify/flash-list';

import React from 'react';
Expand All @@ -30,6 +34,9 @@ type WorkspaceCategoryRulesTableProps<TItem extends CategoryRulesTableItem> = {
selectedKeys: string[];
onRowSelectionChange: (selectedRowKeys: string[]) => void;
headerComponent?: React.ReactElement;

/** The actions the selected rules support, offered in the floating bar the wide layout shows while rows are selected. */
bulkActionOptions?: Array<DropdownOption<DeepValueOf<typeof CONST.POLICY.BULK_ACTION_TYPES>>>;
emptyStateContent?: React.ReactElement;
tableTitle: string;
findRuleLabel: string;
Expand Down Expand Up @@ -59,6 +66,7 @@ function WorkspaceCategoryRulesTableImpl({
emptyState,
renderRow,
typeColumnWidth = variables.tableTypeColumnWidth,
bulkActionOptions,
}: WorkspaceCategoryRulesTableProps<CategoryRulesTableItem>) {
const {localeCompare} = useLocalize();
const styles = useThemeStyles();
Expand Down Expand Up @@ -126,6 +134,7 @@ function WorkspaceCategoryRulesTableImpl({
<Table.NoResultsState />
<Table.Header />
<Table.Body />
{!!bulkActionOptions && <Table.BulkActionBar options={bulkActionOptions} />}
</Table>
);
}
Expand Down
Loading
Loading