Skip to content

Commit 5d0cb25

Browse files
Extract common MoreActionsMenu & MoreActionsIcon components. (#24753)
* Extract common `MoreActionsMenu` & `MoreActionsIcon` components. * Removing obsolete prop. * Always showing in portal. * Allowing to specify size, using in more data tables. * Exporting from directory index. * Adding title to "More Actions" button, adjusting tests. * Use solid buttons for search/dashboard.
1 parent e6827c0 commit 5d0cb25

15 files changed

Lines changed: 128 additions & 70 deletions

File tree

graylog2-web-interface/src/components/bootstrap/DropdownButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type Props = React.PropsWithChildren<
4646
onToggle?: (isOpen: boolean) => void;
4747
pullRight?: boolean;
4848
title?: React.ReactNode;
49+
withinPortal?: boolean;
4950
}
5051
>;
5152

@@ -67,13 +68,15 @@ const DropdownButton = ({
6768
pullRight = undefined,
6869
noCaret = undefined,
6970
keepMounted = undefined,
71+
withinPortal = undefined,
7072
...rest
7173
}: Props) => (
7274
<Menu
7375
position={position(pullRight, dropup)}
7476
onChange={onToggle}
7577
keepMounted={keepMounted}
76-
closeOnItemClick={closeOnItemClick}>
78+
closeOnItemClick={closeOnItemClick}
79+
withinPortal={withinPortal}>
7780
<Menu.Target>
7881
<Button onClick={onMouseDown} aria-label={buttonTitle} {...rest} title={buttonTitle}>
7982
{title}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (C) 2020 Graylog, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*/
17+
import * as React from 'react';
18+
import styled from 'styled-components';
19+
20+
import { Icon } from 'components/common/index';
21+
import { DropdownButton } from 'components/bootstrap';
22+
import type { StyleProps } from 'components/bootstrap/Button';
23+
import type { SizeProp } from 'components/common/Icon';
24+
25+
type MoreActionsIconProps = {
26+
size?: SizeProp;
27+
};
28+
export const MoreActionsIcon = ({ size = undefined }: MoreActionsIconProps) => <Icon name="more_horiz" size={size} />;
29+
30+
type MoreActionsMenuProps = {
31+
'aria-label'?: string;
32+
size?: SizeProp;
33+
bsStyle?: StyleProps;
34+
className?: string;
35+
id?: string;
36+
keepMounted?: boolean;
37+
pullRight?: boolean;
38+
title?: string;
39+
solid?: boolean;
40+
};
41+
const StyledDropdownButton = styled(DropdownButton)<{ $transparent?: boolean }>`
42+
${({ $transparent }) => ($transparent ? 'background-color: transparent;' : '')}
43+
`;
44+
export const MoreActionsMenu = ({
45+
'aria-label': ariaLabel,
46+
size = 'xs',
47+
bsStyle = undefined,
48+
children = undefined,
49+
className = undefined,
50+
id = undefined,
51+
keepMounted = undefined,
52+
pullRight = undefined,
53+
title = 'More Actions',
54+
solid = false,
55+
}: React.PropsWithChildren<MoreActionsMenuProps>) => (
56+
<StyledDropdownButton
57+
$transparent={!solid}
58+
aria-label={ariaLabel}
59+
bsStyle={bsStyle}
60+
buttonTitle={title}
61+
className={className}
62+
id={id}
63+
keepMounted={keepMounted}
64+
noCaret
65+
pullRight={pullRight}
66+
title={<MoreActionsIcon size={size} />}
67+
withinPortal>
68+
{children}
69+
</StyledDropdownButton>
70+
);

graylog2-web-interface/src/components/common/Pagination.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { Pagination as BootstrapPagination } from 'react-bootstrap';
2020
import { createUltimatePagination, ITEM_TYPES } from 'react-ultimate-pagination';
2121
import styled, { css } from 'styled-components';
2222

23+
import { MoreActionsIcon } from 'components/common/MoreActions';
24+
2325
import Icon from './Icon';
2426

2527
type Props = {
@@ -113,7 +115,7 @@ const UltimatePagination = createUltimatePagination({
113115
title={title}
114116
aria-label={title}
115117
className="pagination-control">
116-
<Icon name="more_horiz" />
118+
<MoreActionsIcon />
117119
</BootstrapPagination.Ellipsis>
118120
);
119121
},

graylog2-web-interface/src/components/common/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export { default as LocaleSelect } from './LocaleSelect';
8383
export { default as Markdown } from './Markdown';
8484
export { default as MessageDetailsDefinitionList } from './MessageDetailsDefinitionList';
8585
export { default as ModalSubmit } from './ModalSubmit';
86+
export { MoreActionsIcon, MoreActionsMenu } from './MoreActions';
8687
export { default as MultiSelect } from './MultiSelect';
8788
export { default as NavTabs } from './NavTabs';
8889
export { default as NestedForm } from './NestedForm';

graylog2-web-interface/src/components/lookup-tables/adapter-list/list.test.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ jest.mock('components/lookup-tables/hooks/useLookupTablesAPI', () => ({
8080
}),
8181
}));
8282

83+
const moreActionsName = { name: new RegExp(`More Actions for ${DATA_ADAPTERS[0].name}`, 'i') };
84+
8385
describe('Data Adapter List', () => {
8486
it('should render a list of data adapters', async () => {
8587
render(<DataAdapterList />);
@@ -98,21 +100,21 @@ describe('Data Adapter List', () => {
98100
it('should show an actions menu', async () => {
99101
render(<DataAdapterList />);
100102

101-
await screen.findByRole('button', { name: DATA_ADAPTERS[0].id });
103+
await screen.findByRole('button', moreActionsName);
102104
});
103105

104106
it('should be able to edit a data adapter', async () => {
105107
render(<DataAdapterList />);
106108

107-
userEvent.click(await screen.findByRole('button', { name: DATA_ADAPTERS[0].id }));
109+
userEvent.click(await screen.findByRole('button', moreActionsName));
108110

109111
await screen.findByRole('menuitem', { name: /edit/i });
110112
});
111113

112114
it('should be able to delete a data adapter', async () => {
113115
render(<DataAdapterList />);
114116

115-
userEvent.click(await screen.findByRole('button', { name: DATA_ADAPTERS[0].id }));
117+
userEvent.click(await screen.findByRole('button', moreActionsName));
116118
userEvent.click(await screen.findByRole('menuitem', { name: /delete/i }));
117119
userEvent.click(await screen.findByRole('button', { name: /delete/i }));
118120

graylog2-web-interface/src/components/lookup-tables/adapter-list/use-actions.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import { useCallback, useState } from 'react';
1919
import { useNavigate } from 'react-router-dom';
2020

2121
import Routes from 'routing/Routes';
22-
import { MenuItem, DeleteMenuItem, DropdownButton, BootstrapModalConfirm } from 'components/bootstrap';
23-
import { Icon, Spinner } from 'components/common';
22+
import { MenuItem, DeleteMenuItem, BootstrapModalConfirm } from 'components/bootstrap';
23+
import { Spinner } from 'components/common';
2424
import useScopePermissions from 'hooks/useScopePermissions';
2525
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
2626
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
2727
import { useDeleteDataAdapter } from 'components/lookup-tables/hooks/useLookupTablesAPI';
2828
import type { DataAdapterEntity } from 'components/lookup-tables/types';
29+
import { MoreActionsMenu } from 'components/common/MoreActions';
2930

3031
type ActionsProps = {
3132
adapter: DataAdapterEntity;
@@ -57,17 +58,11 @@ function Actions({ adapter }: ActionsProps) {
5758

5859
return (
5960
<>
60-
<DropdownButton
61-
bsStyle="transparent"
62-
title={<Icon name="more_horiz" size="lg" />}
63-
id={adapter.id}
64-
buttonTitle={adapter.id}
65-
noCaret
66-
pullRight>
61+
<MoreActionsMenu id={adapter.id} size="lg" pullRight title={`More Actions for ${adapter.name}`}>
6762
<MenuItem onSelect={handleEdit}>Edit</MenuItem>
6863
<MenuItem divider />
6964
<DeleteMenuItem onSelect={() => setShowDeleteModal(true)}>Delete</DeleteMenuItem>
70-
</DropdownButton>
65+
</MoreActionsMenu>
7166
{showDeleteModal && (
7267
<BootstrapModalConfirm
7368
showModal

graylog2-web-interface/src/components/lookup-tables/cache-list/list.test.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ jest.mock('components/lookup-tables/hooks/useLookupTablesAPI', () => ({
7676
}),
7777
}));
7878

79+
const moreActionsName = { name: new RegExp(`More Actions for ${CACHES[0].name}`, 'i') };
80+
7981
describe('Cache List', () => {
8082
it('should render a list of caches', async () => {
8183
render(<CacheList />);
@@ -88,21 +90,21 @@ describe('Cache List', () => {
8890
it('should show an actions menu', async () => {
8991
render(<CacheList />);
9092

91-
await screen.findByRole('button', { name: CACHES[0].id });
93+
await screen.findByRole('button', moreActionsName);
9294
});
9395

9496
it('should be able to edit a cache', async () => {
9597
render(<CacheList />);
9698

97-
userEvent.click(await screen.findByRole('button', { name: CACHES[0].id }));
99+
userEvent.click(await screen.findByRole('button', moreActionsName));
98100

99101
await screen.findByRole('menuitem', { name: /edit/i });
100102
});
101103

102104
it('should be able to delete a cache', async () => {
103105
render(<CacheList />);
104106

105-
userEvent.click(await screen.findByRole('button', { name: CACHES[0].id }));
107+
userEvent.click(await screen.findByRole('button', moreActionsName));
106108
userEvent.click(await screen.findByRole('menuitem', { name: /delete/i }));
107109
userEvent.click(await screen.findByRole('button', { name: /delete/i }));
108110

graylog2-web-interface/src/components/lookup-tables/cache-list/use-actions.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import { useCallback, useState } from 'react';
1919
import { useNavigate } from 'react-router-dom';
2020

2121
import Routes from 'routing/Routes';
22-
import { MenuItem, DeleteMenuItem, DropdownButton, BootstrapModalConfirm } from 'components/bootstrap';
23-
import { Icon, Spinner } from 'components/common';
22+
import { MenuItem, DeleteMenuItem, BootstrapModalConfirm } from 'components/bootstrap';
23+
import { Spinner } from 'components/common';
2424
import useScopePermissions from 'hooks/useScopePermissions';
2525
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
2626
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
2727
import { useDeleteCache } from 'components/lookup-tables/hooks/useLookupTablesAPI';
2828
import type { CacheEntity } from 'components/lookup-tables/types';
29+
import { MoreActionsMenu } from 'components/common/MoreActions';
2930

3031
type ActionsProps = {
3132
cache: CacheEntity;
@@ -57,17 +58,11 @@ function Actions({ cache }: ActionsProps) {
5758

5859
return (
5960
<>
60-
<DropdownButton
61-
bsStyle="transparent"
62-
title={<Icon name="more_horiz" size="lg" />}
63-
id={cache.id}
64-
buttonTitle={cache.id}
65-
noCaret
66-
pullRight>
61+
<MoreActionsMenu id={cache.id} size="lg" pullRight title={`More Actions for ${cache.name}`}>
6762
<MenuItem onSelect={handleEdit}>Edit</MenuItem>
6863
<MenuItem divider />
6964
<DeleteMenuItem onSelect={() => setShowDeleteModal(true)}>Delete</DeleteMenuItem>
70-
</DropdownButton>
65+
</MoreActionsMenu>
7166
{showDeleteModal && (
7267
<BootstrapModalConfirm
7368
showModal

graylog2-web-interface/src/components/lookup-tables/lookup-table-list/list.test.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ jest.mock('components/lookup-tables/hooks/useLookupTablesAPI', () => ({
8484
}),
8585
}));
8686

87+
const moreActionsName = { name: new RegExp(`More Actions for ${LOOKUP_TABLES[0].name}`, 'i') };
88+
8789
describe('Lookup Table List', () => {
8890
it('should render a list of lookup tables', async () => {
8991
render(<LookupTableList />);
@@ -112,21 +114,21 @@ describe('Lookup Table List', () => {
112114
it('should show an actions menu', async () => {
113115
render(<LookupTableList />);
114116

115-
await screen.findByRole('button', { name: LOOKUP_TABLES[0].id });
117+
await screen.findByRole('button', moreActionsName);
116118
});
117119

118120
it('should be able to edit a table', async () => {
119121
render(<LookupTableList />);
120122

121-
userEvent.click(await screen.findByRole('button', { name: LOOKUP_TABLES[0].id }));
123+
userEvent.click(await screen.findByRole('button', moreActionsName));
122124

123125
await screen.findByRole('menuitem', { name: /edit/i });
124126
});
125127

126128
it('should be able to delete a table', async () => {
127129
render(<LookupTableList />);
128130

129-
userEvent.click(await screen.findByRole('button', { name: LOOKUP_TABLES[0].id }));
131+
userEvent.click(await screen.findByRole('button', moreActionsName));
130132
userEvent.click(await screen.findByRole('menuitem', { name: /delete/i }));
131133
userEvent.click(await screen.findByRole('button', { name: /delete/i }));
132134

graylog2-web-interface/src/components/lookup-tables/lookup-table-list/use-actions.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import { useCallback, useState } from 'react';
1919
import { useNavigate } from 'react-router-dom';
2020

2121
import Routes from 'routing/Routes';
22-
import { MenuItem, DeleteMenuItem, DropdownButton, BootstrapModalConfirm } from 'components/bootstrap';
23-
import { Icon, Spinner } from 'components/common';
22+
import { MenuItem, DeleteMenuItem, BootstrapModalConfirm } from 'components/bootstrap';
23+
import { Spinner } from 'components/common';
2424
import useScopePermissions from 'hooks/useScopePermissions';
2525
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
2626
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
2727
import { useDeleteLookupTable } from 'components/lookup-tables/hooks/useLookupTablesAPI';
2828
import type { LookupTableEntity } from 'components/lookup-tables/types';
29+
import { MoreActionsMenu } from 'components/common/MoreActions';
2930

3031
type ActionsProps = {
3132
lut: LookupTableEntity;
@@ -57,17 +58,11 @@ function Actions({ lut }: ActionsProps) {
5758

5859
return (
5960
<>
60-
<DropdownButton
61-
bsStyle="transparent"
62-
title={<Icon name="more_horiz" size="lg" />}
63-
id={lut.id}
64-
buttonTitle={lut.id}
65-
noCaret
66-
pullRight>
61+
<MoreActionsMenu id={lut.id} size="lg" pullRight title={`More Actions for ${lut.name}`}>
6762
<MenuItem onSelect={handleEdit}>Edit</MenuItem>
6863
<MenuItem divider />
6964
<DeleteMenuItem onSelect={() => setShowDeleteModal(true)}>Delete</DeleteMenuItem>
70-
</DropdownButton>
65+
</MoreActionsMenu>
7166
{showDeleteModal && (
7267
<BootstrapModalConfirm
7368
showModal

0 commit comments

Comments
 (0)