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

Commit 5a9c2e5

Browse files
author
Kerry
authored
Device manager - selectable device tile wrapper (PSG-637) (#9153)
* add selectabledevicetile wrapper * set pointer cursor * line up own device icon with new checkboxes
1 parent 5fbeb20 commit 5a9c2e5

File tree

10 files changed

+255
-23
lines changed

10 files changed

+255
-23
lines changed

res/css/_components.pcss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
@import "./components/views/messages/_MBeaconBody.pcss";
2929
@import "./components/views/messages/shared/_MediaProcessingError.pcss";
3030
@import "./components/views/settings/devices/_DeviceTile.pcss";
31+
@import "./components/views/settings/devices/_SelectableDeviceTile.pcss";
3132
@import "./components/views/settings/shared/_SettingsSubsection.pcss";
3233
@import "./components/views/spaces/_QuickThemeSwitcher.pcss";
3334
@import "./structures/_AutoHideScrollbar.pcss";
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
.mx_SelectableDeviceTile {
18+
display: flex;
19+
flex-direction: row;
20+
align-items: center;
21+
width: 100%;
22+
cursor: pointer;
23+
}
24+
25+
.mx_SelectableDeviceTile_checkbox {
26+
flex: 0 0;
27+
margin-right: $spacing-16;
28+
}

res/css/views/settings/_DevicesPanel.pcss

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ limitations under the License.
5656
align-items: flex-start;
5757
margin-block: 10px;
5858
min-height: 35px;
59+
padding: 0 $spacing-8;
5960
}
6061

61-
.mx_DevicesPanel_icon, .mx_DevicesPanel_checkbox {
62-
margin-left: 9px;
62+
.mx_DevicesPanel_icon {
63+
margin-left: 0px;
64+
margin-right: $spacing-16;
6365
margin-top: 2px;
6466
}
6567

src/components/views/elements/StyledCheckbox.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
7070
<div className="mx_Checkbox_background">
7171
<div className="mx_Checkbox_checkmark" />
7272
</div>
73-
<div>
74-
{ this.props.children }
75-
</div>
73+
{ !!this.props.children &&
74+
<div>
75+
{ this.props.children }
76+
</div>
77+
}
7678
</label>
7779
</span>;
7880
}

src/components/views/settings/DevicesPanelEntry.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ import { logger } from "matrix-js-sdk/src/logger";
2020

2121
import { _t } from '../../../languageHandler';
2222
import { MatrixClientPeg } from '../../../MatrixClientPeg';
23-
import StyledCheckbox, { CheckboxStyle } from '../elements/StyledCheckbox';
2423
import AccessibleButton from "../elements/AccessibleButton";
2524
import Field from "../elements/Field";
2625
import Modal from "../../../Modal";
2726
import SetupEncryptionDialog from '../dialogs/security/SetupEncryptionDialog';
2827
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
2928
import LogoutDialog from '../dialogs/LogoutDialog';
3029
import DeviceTile from './devices/DeviceTile';
30+
import SelectableDeviceTile from './devices/SelectableDeviceTile';
3131

3232
interface IProps {
3333
device: IMyDevice;
@@ -133,14 +133,6 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
133133
</AccessibleButton>;
134134
}
135135

136-
const left = this.props.isOwnDevice ?
137-
<div className="mx_DevicesPanel_deviceTrust">
138-
<span className={"mx_DevicesPanel_icon mx_E2EIcon " + iconClass} />
139-
</div> :
140-
<div className="mx_DevicesPanel_checkbox">
141-
<StyledCheckbox kind={CheckboxStyle.Outline} onChange={this.onDeviceToggled} checked={this.props.selected} />
142-
</div>;
143-
144136
const buttons = this.state.renaming ?
145137
<form className="mx_DevicesPanel_renameForm" onSubmit={this.onRenameSubmit}>
146138
<Field
@@ -162,12 +154,22 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
162154
</AccessibleButton>
163155
</React.Fragment>;
164156

165-
return (
166-
<div className={"mx_DevicesPanel_device" + myDeviceClass}>
167-
{ left }
157+
if (this.props.isOwnDevice) {
158+
return <div className={"mx_DevicesPanel_device" + myDeviceClass}>
159+
<div className="mx_DevicesPanel_deviceTrust">
160+
<span className={"mx_DevicesPanel_icon mx_E2EIcon " + iconClass} />
161+
</div>
168162
<DeviceTile device={this.props.device}>
169163
{ buttons }
170164
</DeviceTile>
165+
</div>;
166+
}
167+
168+
return (
169+
<div className={"mx_DevicesPanel_device" + myDeviceClass}>
170+
<SelectableDeviceTile device={this.props.device} onClick={this.onDeviceToggled} isSelected={this.props.selected}>
171+
{ buttons }
172+
</SelectableDeviceTile>
171173
</div>
172174
);
173175
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ import TooltipTarget from "../../elements/TooltipTarget";
2323
import { Alignment } from "../../elements/Tooltip";
2424
import Heading from "../../typography/Heading";
2525

26-
interface Props {
26+
export interface DeviceTileProps {
2727
device: IMyDevice;
2828
children?: React.ReactNode;
29+
onClick?: () => void;
2930
}
3031

3132
const DeviceTileName: React.FC<{ device: IMyDevice }> = ({ device }) => {
3233
if (device.display_name) {
3334
return <TooltipTarget
34-
alignment={Alignment.Left}
35+
alignment={Alignment.Top}
3536
label={`${device.display_name} (${device.device_id})`}
3637
>
3738
<Heading size='h4'>
@@ -59,15 +60,15 @@ const DeviceMetadata: React.FC<{ value: string, id: string }> = ({ value, id })
5960
value ? <span data-testid={`device-metadata-${id}`}>{ value }</span> : null
6061
);
6162

62-
const DeviceTile: React.FC<Props> = ({ device, children }) => {
63+
const DeviceTile: React.FC<DeviceTileProps> = ({ device, children, onClick }) => {
6364
const lastActivity = device.last_seen_ts && `${_t('Last activity')} ${formatLastActivity(device.last_seen_ts)}`;
6465
const metadata = [
6566
{ id: 'lastActivity', value: lastActivity },
6667
{ id: 'lastSeenIp', value: device.last_seen_ip },
6768
];
6869

6970
return <div className="mx_DeviceTile">
70-
<div className="mx_DeviceTile_info">
71+
<div className="mx_DeviceTile_info" onClick={onClick}>
7172
<DeviceTileName device={device} />
7273
<div className="mx_DeviceTile_metadata">
7374
{ metadata.map(({ id, value }, index) =>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 React from 'react';
18+
19+
import StyledCheckbox, { CheckboxStyle } from '../../elements/StyledCheckbox';
20+
import DeviceTile, { DeviceTileProps } from './DeviceTile';
21+
22+
interface Props extends DeviceTileProps {
23+
isSelected: boolean;
24+
onClick: () => void;
25+
}
26+
27+
const SelectableDeviceTile: React.FC<Props> = ({ children, device, isSelected, onClick }) => {
28+
return <div className='mx_SelectableDeviceTile'>
29+
<StyledCheckbox
30+
kind={CheckboxStyle.Solid}
31+
checked={isSelected}
32+
onChange={onClick}
33+
className='mx_SelectableDeviceTile_checkbox'
34+
id={`device-tile-checkbox-${device.device_id}`}
35+
/>
36+
<DeviceTile device={device} onClick={onClick}>
37+
{ children }
38+
</DeviceTile>
39+
</div>;
40+
};
41+
42+
export default SelectableDeviceTile;

test/components/views/elements/__snapshots__/LabelledCheckbox-test.tsx.snap

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ exports[`<LabelledCheckbox /> should render with byline of "this is a byline" 1`
3434
className="mx_Checkbox_checkmark"
3535
/>
3636
</div>
37-
<div />
3837
</label>
3938
</span>
4039
</StyledCheckbox>
@@ -90,7 +89,6 @@ exports[`<LabelledCheckbox /> should render with byline of null 1`] = `
9089
className="mx_Checkbox_checkmark"
9190
/>
9291
</div>
93-
<div />
9492
</label>
9593
</span>
9694
</StyledCheckbox>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 { fireEvent, render } from '@testing-library/react';
18+
import React from 'react';
19+
import { act } from 'react-dom/test-utils';
20+
21+
import SelectableDeviceTile from '../../../../../src/components/views/settings/devices/SelectableDeviceTile';
22+
23+
describe('<SelectableDeviceTile />', () => {
24+
const device = {
25+
display_name: 'My Device',
26+
device_id: 'my-device',
27+
last_seen_ip: '123.456.789',
28+
};
29+
const defaultProps = {
30+
onClick: jest.fn(),
31+
device,
32+
children: <div>test</div>,
33+
isSelected: false,
34+
};
35+
const getComponent = (props = {}) =>
36+
(<SelectableDeviceTile {...defaultProps} {...props} />);
37+
38+
it('renders unselected device tile with checkbox', () => {
39+
const { container } = render(getComponent());
40+
expect(container).toMatchSnapshot();
41+
});
42+
43+
it('renders selected tile', () => {
44+
const { container } = render(getComponent({ isSelected: true }));
45+
expect(container.querySelector(`#device-tile-checkbox-${device.device_id}`)).toMatchSnapshot();
46+
});
47+
48+
it('calls onClick on checkbox click', () => {
49+
const onClick = jest.fn();
50+
const { container } = render(getComponent({ onClick }));
51+
52+
act(() => {
53+
fireEvent.click(container.querySelector(`#device-tile-checkbox-${device.device_id}`));
54+
});
55+
56+
expect(onClick).toHaveBeenCalled();
57+
});
58+
59+
it('calls onClick on device tile info click', () => {
60+
const onClick = jest.fn();
61+
const { getByText } = render(getComponent({ onClick }));
62+
63+
act(() => {
64+
fireEvent.click(getByText(device.display_name));
65+
});
66+
67+
expect(onClick).toHaveBeenCalled();
68+
});
69+
70+
it('does not call onClick when clicking device tiles actions', () => {
71+
const onClick = jest.fn();
72+
const onDeviceActionClick = jest.fn();
73+
const children = <button onClick={onDeviceActionClick} data-testid='device-action-button'>test</button>;
74+
const { getByTestId } = render(getComponent({ onClick, children }));
75+
76+
act(() => {
77+
fireEvent.click(getByTestId('device-action-button'));
78+
});
79+
80+
// action click handler called
81+
expect(onDeviceActionClick).toHaveBeenCalled();
82+
// main click handler not called
83+
expect(onClick).not.toHaveBeenCalled();
84+
});
85+
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<SelectableDeviceTile /> renders selected tile 1`] = `
4+
<input
5+
checked=""
6+
id="device-tile-checkbox-my-device"
7+
type="checkbox"
8+
/>
9+
`;
10+
11+
exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1`] = `
12+
<div>
13+
<div
14+
class="mx_SelectableDeviceTile"
15+
>
16+
<span
17+
class="mx_Checkbox mx_SelectableDeviceTile_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
18+
>
19+
<input
20+
id="device-tile-checkbox-my-device"
21+
type="checkbox"
22+
/>
23+
<label
24+
for="device-tile-checkbox-my-device"
25+
>
26+
<div
27+
class="mx_Checkbox_background"
28+
>
29+
<div
30+
class="mx_Checkbox_checkmark"
31+
/>
32+
</div>
33+
</label>
34+
</span>
35+
<div
36+
class="mx_DeviceTile"
37+
>
38+
<div
39+
class="mx_DeviceTile_info"
40+
>
41+
<div
42+
tabindex="0"
43+
>
44+
<h4
45+
class="mx_Heading_h4"
46+
>
47+
My Device
48+
</h4>
49+
</div>
50+
<div
51+
class="mx_DeviceTile_metadata"
52+
>
53+
·
54+
<span
55+
data-testid="device-metadata-lastSeenIp"
56+
>
57+
123.456.789
58+
</span>
59+
</div>
60+
</div>
61+
<div
62+
class="mx_DeviceTile_actions"
63+
>
64+
<div>
65+
test
66+
</div>
67+
</div>
68+
</div>
69+
</div>
70+
</div>
71+
`;

0 commit comments

Comments
 (0)