Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit b2fc4a2

Browse files
author
Kerry Archibald
committed
use kebab context menu in current device section
1 parent 2d540e9 commit b2fc4a2

File tree

8 files changed

+196
-3
lines changed

8 files changed

+196
-3
lines changed

src/components/structures/ContextMenu.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ export interface IProps extends IPosition {
9292
// within an existing FocusLock e.g inside a modal.
9393
focusLock?: boolean;
9494

95+
// call onFinished on any interaction with the menu
96+
closeOnInteraction?: boolean;
97+
9598
// Function to be called on menu close
9699
onFinished();
97100
// on resize callback
@@ -186,6 +189,10 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
186189
private onClick = (ev: React.MouseEvent) => {
187190
// Don't allow clicks to escape the context menu wrapper
188191
ev.stopPropagation();
192+
193+
if (this.props.closeOnInteraction) {
194+
this.props.onFinished?.();
195+
}
189196
};
190197

191198
// We now only handle closing the ContextMenu in this keyDown handler.

src/components/views/settings/devices/CurrentDeviceSection.tsx

+48-2
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,20 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { LocalNotificationSettings } from 'matrix-js-sdk/src/@types/local_notifications';
1817
import React, { useState } from 'react';
18+
import { LocalNotificationSettings } from 'matrix-js-sdk/src/@types/local_notifications';
1919

2020
import { _t } from '../../../../languageHandler';
2121
import Spinner from '../../elements/Spinner';
2222
import SettingsSubsection from '../shared/SettingsSubsection';
23+
import SettingsSubsectionHeading from '../shared/SettingsSubsectionHeading';
2324
import DeviceDetails from './DeviceDetails';
2425
import DeviceExpandDetailsButton from './DeviceExpandDetailsButton';
2526
import DeviceTile from './DeviceTile';
2627
import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard';
2728
import { ExtendedDevice } from './types';
29+
import { KebabContextMenu } from '../../context_menus/KebabContextMenu';
30+
import { IconizedContextMenuOption } from '../../context_menus/IconizedContextMenu';
2831

2932
interface Props {
3033
device?: ExtendedDevice;
@@ -34,9 +37,47 @@ interface Props {
3437
setPushNotifications?: (deviceId: string, enabled: boolean) => Promise<void> | undefined;
3538
onVerifyCurrentDevice: () => void;
3639
onSignOutCurrentDevice: () => void;
40+
signOutAllOtherSessions?: () => void;
3741
saveDeviceName: (deviceName: string) => Promise<void>;
3842
}
3943

44+
type CurrentDeviceSectionHeadingProps =
45+
Pick<Props, 'onSignOutCurrentDevice' | 'signOutAllOtherSessions'>
46+
& { disabled?: boolean };
47+
48+
const CurrentDeviceSectionHeading: React.FC<CurrentDeviceSectionHeadingProps> = ({
49+
onSignOutCurrentDevice,
50+
signOutAllOtherSessions,
51+
disabled,
52+
}) => {
53+
const menuOptions = [
54+
<IconizedContextMenuOption
55+
key="sign-out"
56+
label={_t('Sign out')}
57+
onClick={onSignOutCurrentDevice}
58+
isDestructive
59+
/>,
60+
...(signOutAllOtherSessions
61+
? [
62+
<IconizedContextMenuOption
63+
key="sign-out-all-others"
64+
label={_t('Sign out all other sessions')}
65+
onClick={signOutAllOtherSessions}
66+
isDestructive
67+
/>,
68+
]
69+
: []
70+
),
71+
];
72+
return <SettingsSubsectionHeading heading={_t('Current session')}>
73+
<KebabContextMenu
74+
disabled={disabled}
75+
title={_t('Options')}
76+
options={menuOptions}
77+
/>
78+
</SettingsSubsectionHeading>;
79+
};
80+
4081
const CurrentDeviceSection: React.FC<Props> = ({
4182
device,
4283
isLoading,
@@ -45,13 +86,18 @@ const CurrentDeviceSection: React.FC<Props> = ({
4586
setPushNotifications,
4687
onVerifyCurrentDevice,
4788
onSignOutCurrentDevice,
89+
signOutAllOtherSessions,
4890
saveDeviceName,
4991
}) => {
5092
const [isExpanded, setIsExpanded] = useState(false);
5193

5294
return <SettingsSubsection
53-
heading={_t('Current session')}
5495
data-testid='current-session-section'
96+
heading={<CurrentDeviceSectionHeading
97+
onSignOutCurrentDevice={onSignOutCurrentDevice}
98+
signOutAllOtherSessions={signOutAllOtherSessions}
99+
disabled={isLoading || !device || isSigningOut}
100+
/>}
55101
>
56102
{ /* only show big spinner on first load */ }
57103
{ isLoading && !device && <Spinner /> }

src/components/views/settings/tabs/user/SessionManagerTab.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ const SessionManagerTab: React.FC = () => {
171171
setSelectedDeviceIds([]);
172172
}, [filter, setSelectedDeviceIds]);
173173

174+
const signOutAllOtherSessions = shouldShowOtherSessions ? () => {
175+
onSignOutOtherDevices(Object.keys(otherDevices));
176+
}: undefined;
177+
174178
return <SettingsTab heading={_t('Sessions')}>
175179
<SecurityRecommendations
176180
devices={devices}
@@ -186,6 +190,7 @@ const SessionManagerTab: React.FC = () => {
186190
saveDeviceName={(deviceName) => saveDeviceName(currentDeviceId, deviceName)}
187191
onVerifyCurrentDevice={onVerifyCurrentDevice}
188192
onSignOutCurrentDevice={onSignOutCurrentDevice}
193+
signOutAllOtherSessions={signOutAllOtherSessions}
189194
/>
190195
{
191196
shouldShowOtherSessions &&

src/i18n/strings/en_EN.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,8 @@
17171717
"Please enter verification code sent via text.": "Please enter verification code sent via text.",
17181718
"Verification code": "Verification code",
17191719
"Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.",
1720+
"Sign out": "Sign out",
1721+
"Sign out all other sessions": "Sign out all other sessions",
17201722
"Current session": "Current session",
17211723
"Confirm logging out these devices by using Single Sign On to prove your identity.|other": "Confirm logging out these devices by using Single Sign On to prove your identity.",
17221724
"Confirm logging out these devices by using Single Sign On to prove your identity.|one": "Confirm logging out this device by using Single Sign On to prove your identity.",
@@ -1773,7 +1775,6 @@
17731775
"Not ready for secure messaging": "Not ready for secure messaging",
17741776
"Inactive": "Inactive",
17751777
"Inactive for %(inactiveAgeDays)s days or longer": "Inactive for %(inactiveAgeDays)s days or longer",
1776-
"Sign out": "Sign out",
17771778
"Filter devices": "Filter devices",
17781779
"Show": "Show",
17791780
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s sessions selected",

test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap

+35
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,19 @@ exports[`<CurrentDeviceSection /> handles when device is falsy 1`] = `
128128
>
129129
Current session
130130
</h3>
131+
<div
132+
aria-disabled="true"
133+
aria-expanded="false"
134+
aria-haspopup="true"
135+
class="mx_AccessibleButton mx_AccessibleButton_disabled"
136+
disabled=""
137+
role="button"
138+
tabindex="0"
139+
>
140+
<div
141+
class="mx_KebabContextMenu_icon"
142+
/>
143+
</div>
131144
</div>
132145
<div
133146
class="mx_SettingsSubsection_content"
@@ -150,6 +163,17 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
150163
>
151164
Current session
152165
</h3>
166+
<div
167+
aria-expanded="false"
168+
aria-haspopup="true"
169+
class="mx_AccessibleButton"
170+
role="button"
171+
tabindex="0"
172+
>
173+
<div
174+
class="mx_KebabContextMenu_icon"
175+
/>
176+
</div>
153177
</div>
154178
<div
155179
class="mx_SettingsSubsection_content"
@@ -274,6 +298,17 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
274298
>
275299
Current session
276300
</h3>
301+
<div
302+
aria-expanded="false"
303+
aria-haspopup="true"
304+
class="mx_AccessibleButton"
305+
role="button"
306+
tabindex="0"
307+
>
308+
<div
309+
class="mx_KebabContextMenu_icon"
310+
/>
311+
</div>
277312
</div>
278313
<div
279314
class="mx_SettingsSubsection_content"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { render } from '@testing-library/react';
18+
import React from 'react';
19+
20+
import SettingsSubsectionHeading from '../../../../../src/components/views/settings/shared/SettingsSubsectionHeading';
21+
22+
describe('<SettingsSubsectionHeading />', () => {
23+
const defaultProps = {
24+
heading: 'test',
25+
};
26+
const getComponent = (props = {}) =>
27+
render(<SettingsSubsectionHeading {...defaultProps} {...props} />);
28+
29+
it('renders without children', () => {
30+
const { container } = getComponent();
31+
expect({ container }).toMatchSnapshot();
32+
});
33+
34+
it('renders with children', () => {
35+
const children = <a href='/#'>test</a>;
36+
const { container } = getComponent({ children });
37+
expect({ container }).toMatchSnapshot();
38+
});
39+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<SettingsSubsectionHeading /> renders with children 1`] = `
4+
Object {
5+
"container": <div>
6+
<div
7+
class="mx_SettingsSubsectionHeading"
8+
>
9+
<h3
10+
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
11+
>
12+
test
13+
</h3>
14+
<a
15+
href="/#"
16+
>
17+
test
18+
</a>
19+
</div>
20+
</div>,
21+
}
22+
`;
23+
24+
exports[`<SettingsSubsectionHeading /> renders without children 1`] = `
25+
Object {
26+
"container": <div>
27+
<div
28+
class="mx_SettingsSubsectionHeading"
29+
>
30+
<h3
31+
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
32+
>
33+
test
34+
</h3>
35+
</div>
36+
</div>,
37+
}
38+
`;

test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap

+22
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ exports[`<SessionManagerTab /> renders current session section with a verified s
8989
>
9090
Current session
9191
</h3>
92+
<div
93+
aria-expanded="false"
94+
aria-haspopup="true"
95+
class="mx_AccessibleButton"
96+
role="button"
97+
tabindex="0"
98+
>
99+
<div
100+
class="mx_KebabContextMenu_icon"
101+
/>
102+
</div>
92103
</div>
93104
<div
94105
class="mx_SettingsSubsection_content"
@@ -199,6 +210,17 @@ exports[`<SessionManagerTab /> renders current session section with an unverifie
199210
>
200211
Current session
201212
</h3>
213+
<div
214+
aria-expanded="false"
215+
aria-haspopup="true"
216+
class="mx_AccessibleButton"
217+
role="button"
218+
tabindex="0"
219+
>
220+
<div
221+
class="mx_KebabContextMenu_icon"
222+
/>
223+
</div>
202224
</div>
203225
<div
204226
class="mx_SettingsSubsection_content"

0 commit comments

Comments
 (0)